If you have searched "Node.js Express alternative" recently, you have probably seen the same three answers everyone gives: Fastify, Hono, Elysia. They are all good. I have used all of them. But after a decade of building APIs, the framework I actually reach for now is DaloyJS (@daloyjs/core), and this post is my honest pitch for why it is the best Express alternative for Node.js in 2026, written for people who are still early in their backend journey.
No gatekeeping, no "you should already know this." Just the reasons.
First, what is wrong with Express?
Nothing, exactly. Express is fine. It is also from 2010, and it shows. Express gives you a router and a middleware pipeline, and then it says "good luck with the rest." The rest includes:
- Validation (you add it)
- API docs (you add it, then babysit it forever)
- Types that flow from your routes into your handlers (you do not get these)
- A pile of security defaults (you add them, if you remember they exist)
For a beginner, that "you add it" list is a trap. You do not know what you do not know, so you ship an API with no body-size limit, no rate limiting, and a req.body typed as any, and you find out it was a problem when something breaks in production. Been there. The framework let me.
A good Express alternative should close that gap by default. That is the whole bar.
What makes DaloyJS different
DaloyJS is contract-first. You define a route once, and that single definition gives you validation, types, docs, and a client SDK. Here is a complete, runnable example:
import { App, NotFoundError } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
import { z } from "zod";
const app = new App({
openapi: { info: { title: "Bookstore", version: "1.0.0" } },
docs: true, // live API docs at /docs, for free
});
app.route({
method: "GET",
path: "/books/:id",
operationId: "getBookById",
request: { params: z.object({ id: z.string() }) },
responses: {
200: { description: "Book found", body: z.object({ id: z.string(), title: z.string() }) },
404: { description: "Not found" },
},
handler: async ({ params }) => {
const book = await findBook(params.id);
if (!book) throw new NotFoundError(`No book ${params.id}`);
return { status: 200, body: book };
},
});
serve(app, { port: 3000 });
Look at what that one route gave you, none of which you wrote separately:
-
Validation:
params.idis checked before your handler runs. -
Types: inside the handler,
params.idis astring, and the200body is type-checked against the schema. -
Docs: a real, clickable API docs page at
/docs, plus/openapi.json. - A typed client SDK: run one command and your frontend gets a fully typed fetch client.
In Express you would need express-validator, a hand-maintained Swagger file, and a separate codegen step to get the same things, and they would all drift out of sync the first time you renamed a field. Here they cannot drift, because they all come from the same route.
The reason that actually matters: security defaults
This is the part I wish someone had hammered into me as a junior. An Express alternative that only gives you nicer ergonomics is not enough. The thing that should make you switch is the defaults.
DaloyJS ships with, on by default or one line away:
- A body-size cap, so nobody can crash your server with a giant payload.
- Request timeouts, so slow requests do not pile up.
- Prototype-pollution-safe JSON parsing (
__proto__in a payload cannot poison your objects). - Secure headers, rate limiting, CRLF/header-injection rejection.
- Production error redaction, so you do not leak stack traces to strangers.
- JWT algorithm allowlists and constant-time credential checks for auth.
Here is why this matters more than ever: a lot of us now write code with AI assistants. When you tell an AI "build an endpoint that saves this to the database," it builds exactly that and nothing more. It does not add a body cap or rate limiting, because you did not ask. With Express, that missing security is your problem. With DaloyJS, it is the default, and you would have to actively remove it to get the insecure version. That is the right way around.
"But is it actually production-ready?"
Fair question, and the honest answer is: it is built to be. Zero runtime dependencies in the core (smaller attack surface), structured logging, graceful shutdown, and it runs on Node, Bun, Deno, Cloudflare Workers, and Vercel Edge from the same code. So the "Node.js Express alternative" framing is a bit narrow, the same app runs on the edge too, which Express cannot do without a rewrite.
How to try it
pnpm create daloy@latest
That scaffolds a project with the security defaults already wired, an example route, and the docs page running. Poke at /docs, change a schema, watch the docs update. The first time you see the docs change on their own, you will understand the appeal faster than I can explain it.
So, is it the best Express alternative?
For a brand-new Node.js service in 2026, I genuinely think so, and not because the syntax is prettier. It is because the things you would otherwise forget (validation, docs, the security checklist) are the defaults instead of homework. Fastify, Hono, and Elysia are all great, and if your team already loves one of them, stay. But if you are starting fresh and you want a framework that protects you from the mistakes I made as a junior, this is the one I would hand you.
Already convinced and want to move an existing app? I wrote a step-by-step companion: migrating from Express to DaloyJS, a junior-friendly Node.js migration guide. This post is the why. That one is the how.
Docs and source: daloyjs.dev and github.com/daloyjs/daloy.
Top comments (0)