The flow I wished I had: why we built DaloyJS
Ten years of shipping fullstack apps, one Filipino dev in Norway, and the framework I kept wishing existed at 2am.
I'm Devlin — a Filipino developer who has been writing fullstack web apps for a little over ten years, and who now does it from a small flat in Norway where the sun, depending on the season, either refuses to set or refuses to show up. I drink a lot of coffee. I've also shipped a lot of bad APIs, which is the more relevant credential here.
This post is the long answer to a question I keep getting: anotherTypeScript web framework, really? Why? My short answer is: yes, really, because after a decade of Express, then Koa, then Fastify, then Nest, then Hono, then writing my own "just a tiny layer" libraries six different times, I got tired of the same three problems. So we built DaloyJS to solve them. That's the pitch.
Okay fine, longer version below. Bring coffee. Or in my case, a very expensive Norwegian cup of something that costs more than my first PC mouse.
The three problems that wouldn't go away
In ten years of fullstack work, across startups, consultancies, and one bank that shall remain nameless, the same three problems kept showing up:
- The contract always drifts.The route definition, the Zod (or Joi, or Yup, or class-validator) schema, the OpenAPI YAML, the frontend types, and the client SDK are five different files that pretend to agree with each other. They don't. They never do. The QA team finds out first. The customer finds out second.
- Security defaults are opt-in.Body limits, request timeouts, prototype-pollution-safe JSON, path-traversal rejection, 5xx redaction in prod — all of these are "just add this middleware". Which means in real codebases, under deadline, with three Jira tickets open, they are just… not there.
- The runtime is a prison.You picked Express in 2018. Now it's 2026, your CFO wants Cloudflare Workers because the bill is scary, and your code physically cannot run there. You rewrite. Again.
None of these are new complaints. What's new is that the JavaScript ecosystem finally has the pieces to fix them properly — Standard Schema, OpenAPI 3.1, the Web Fetch API as a portable runtime contract, OpenTelemetry semantic conventions, pnpm with proper supply-chain controls. The pieces exist. They just weren't assembled in one place with one opinion. So we did that. And we called it Daloy, which means flow in Tagalog, because everything flows from one contract.
The "before" picture (you've written this code)
Here's the shape of code I've been writing — and reading in PRs — for years. You will recognize it. You probably wrote some this week.
Look at all those places where truth can hide. The schema knows what a valid request looks like. The handler knows what statuses it returns. The YAML file knows what the world is told it returns. The generated client knows what the YAML said two sprints ago. None of them are checked against each other. The compiler is happily humming along, oblivious, like me eating lunch while my deploy is failing in another tab.
The "after" picture: one route, one truth
Here's the same idea in DaloyJS. One route definition. The validation, the response shape, the OpenAPI operation, the typed client method, and the contract test all come from this single object. If it compiles, they agree.
A few things to notice, because they matter more than they look:
requestandresponsesuse Standard Schema, so you can bring Zod, Valibot, ArkType, whatever you want. I used Zod here because Zod is what I have muscle memory for. Use what makes you happy.operationIdis what becomes the method name on your typed client. Give it a verb-y name now and your frontend devs will mention you in a positive review someday.bodyLimitBytesandrequestTimeoutMsare arguments toApp, not optional middleware you forgot to add. The core enforces them. If a 5xx happens in production, the body is redacted by default, not leaked.secureHeaders(),rateLimit(),requestId()are first-party. They live in the same repo, they get the same tests, they ship on the same release cadence. No more "oh that package is unmaintained since 2021".
The typed client: my favorite part, honestly
Run pnpm genand you get a real fetch-based SDK, generated from the real OpenAPI spec, which was generated from the real route. No hand-written types. No "let me ping the backend dev to ask what 422 returns". The frontend sees this:
That if (result.status === 200) branch is a real discriminated union. TypeScript will yell at you, in red, in your editor, before you even reach for git commit, if you try to read result.body.title from the 404 branch. This is the part where I quietly celebrate and pretend it was always this simple.
Runtime portability without the PowerPoint slides
I've been burned by "runs everywhere" promises before. So I'll just show you. The same app from the example above runs in all of these:
The core only ever sees Request in, Response out. Adapters live at the edges where the runtime quirks live. That means when your CFO discovers Cloudflare Workers next quarter, your team changes one import, not the shape of your application. Beautiful. Boring. Both, simultaneously.
What we're actually solving, in plain words
If I had to put DaloyJS on a single index card and tape it to my monitor, here's what it would say:
One contract per route. Security on by default. The same app on Node, Bun, Deno, Cloudflare, and Vercel Edge. A typed client that's actually typed. Tracing, streaming, and sessions that don't need a PhD. And a supply chain you can sleep through the night with.
That's the whole product. Everything in the docs is a consequence of that index card.
What it is not
I have to say this part too, because frameworks get oversold and then we all end up sad on Hacker News.
- It is not a frontend framework. Use Next.js, React, Remix, Astro, htmx, whatever. DaloyJS is the API on the other side of the wire.
- It is not magical. There is no decorator party, no metadata reflector, no dependency-injection container that needs a 40-page chapter. If it looks like a function, it's a function.
- It is not trying to replace your ORM, your queue, your auth provider, or your email vendor. We have adapters and guidesfor those — because in real life you're going to use Prisma, or Drizzle, or whatever your team already loves.
- It is not finished. Software never is. But it's 320 of 320 tests passing, 100% line and function coverage, strict TypeScript 6, and shipping. That is, in my experience, much better than "done".
Why I, personally, kept showing up
Ten years in, the bugs that still wake me up are not the clever ones. They're the dumb ones. A status code that the docs lied about. A payload field that quietly turned into null. A middleware that "everyone uses" that wasn't wired up in the prod build. A deploy that worked in Node 20 and exploded in a Worker because someone reached for process.env like it was 2015.
DaloyJS is, very honestly, the framework I wanted to hand my younger self when he was crying into his keyboard at 2am Manila time trying to figure out why staging returned HTML for a JSON endpoint. (It was an nginx error page. It is always an nginx error page.) It won't make you a better developer, and it definitely won't make the coffee in Oslo cheaper. But it will, I hope, take a small pile of recurring problems off your desk so you can go solve the actually interesting ones.
Try it in five minutes
If you've read this far, you should just try it. Five minutes. Worst case, you close the tab and we're still friends.
Then read Getting started, poke at the generated OpenAPI, and let me know what breaks. Especially let me know what breaks. A framework only earns the right to exist by surviving other people's real code.
Thanks for reading. Now if you'll excuse me, the sun in Norway just set at 11pm, and I still have not emotionally accepted that as a normal thing.
— Devlin