When I started building APIs, I spent way too much of my life keeping three things in sync: the actual route code, the validation, and the docs. You change one field, you forget the other two, and a week later someone on the frontend team pings you asking why the docs say title but the API returns name. Been there. Shipped that bug. Apologized in standup.
DaloyJS is the framework I wish I had back then. Let me walk you through what it is, in plain language, without assuming you have ten years of backend scars.
The one-sentence version
DaloyJS is a TypeScript web framework where you define a route once, and from that single definition you get validation, types, OpenAPI docs, and a typed client SDK. No copy-pasting. No drifting docs.
If you have heard of FastAPI in the Python world, that “write it once, get docs for free” feeling is exactly what DaloyJS brings to TypeScript.
Why should a junior developer care?
Here is the thing nobody tells you early on: most of the pain in backend work is not writing the logic. It is keeping everything that describes your logic honest. The docs, the input validation, the response shapes, the client code. They all want to lie to you the moment you stop watching them.
DaloyJS makes one definition the single source of truth. You write a route, and that route is the contract. That is what “contract-first” means. The framework reads your contract and generates the boring stuff for you.
What a route looks like
Here is a real example. Do not worry about understanding every line yet, just look at the shape of it.
import { App, NotFoundError } from "@daloyjs/core";
import { z } from "zod";
const BookSchema = z.object({ id: z.string(), title: z.string() });
const app = new App({
openapi: { info: { title: "Bookstore API", version: "1.0.0" } },
docs: true,
});
app.route({
method: "GET",
path: "/books/:id",
operationId: "getBookById",
request: { params: z.object({ id: z.string() }) },
responses: {
200: { description: "Book found", body: BookSchema },
404: { description: "Book not found" },
},
handler: async ({ params }) => {
const book = await findBook(params.id);
if (!book) throw new NotFoundError(`No book with id ${params.id}`);
return { status: 200, body: book };
},
});
Notice what you did here without realizing it:
- You said the URL param
idis a string, so DaloyJS validates it for you. - You said a
200response returns aBookSchema, so your editor knows the exact shape ofbook. - You set
docs: true, so the framework mounts a live docs page at/docsautomatically.
That last point is my favorite. You did not write a single line of documentation, and you still get a real, clickable API docs page. The first time I saw that, I genuinely felt a little robbed of all the hours I used to spend on Swagger config.
The “free stuff” you get from one route
1. Automatic API docs
Set docs: true and DaloyJS mounts /docs, /openapi.json, and /openapi.yaml. Your docs are generated from the same route you already wrote, so they cannot drift out of sync. When you change the route, the docs change with it. No more “the docs lied to me” tickets.
2. Validation you do not have to wire up
The schemas in request and responses are not decoration. DaloyJS actually validates incoming data against them. Send a request with a missing field, and you get a clean, standardized error response instead of a mysterious crash deep in your handler.
DaloyJS uses something called Standard Schema, which is a fancy way of saying you are not locked into one validation library. The examples use Zod 4 because it is small and modern, but Valibot, ArkType, and TypeBox all work too. Pick the one your team likes.
3. End-to-end types
Because your route knows the shape of its inputs and outputs, your editor knows too. Inside the handler, params.id is typed as a string. The body you return is checked against BookSchema. If you typo a field name, TypeScript yells at you before your code ever runs. This is the kind of safety net that makes refactoring feel less like defusing a bomb.
4. A generated client SDK
Once your API has an OpenAPI spec, you can run one command and get a fully typed fetch client out of it (DaloyJS uses Hey API for this). Your frontend can call your backend with full autocomplete and type checking, and that client regenerates whenever your contract changes. Less guessing, fewer “what does this endpoint return again?” moments.
It runs basically everywhere
A lot of frameworks quietly assume you are on Node and only Node. DaloyJS is built on the web standard Request to Response model, which is a fancy way of saying its core does not care where it runs. The same app works on Node, Bun, Deno, Cloudflare Workers, and Vercel Edge through small adapters.
For a junior dev this matters more than it sounds. It means the thing you learn today does not become useless the moment your company decides to deploy to the edge or experiment with Bun. Your knowledge travels with you.
Security you get without becoming a security expert
This is the part I wish someone had handed me on day one. Security is hard, and it is really easy to ship something insecure without knowing it. DaloyJS takes the position that bad defaults are bugs, so a bunch of protections are just on out of the box:
- Request body size limits, so someone cannot send you a 2GB payload to knock your server over.
- Request timeouts, so slow requests do not pile up forever.
- JSON parsing that is safe against prototype pollution (a sneaky attack class you will eventually read about and be glad you were protected from).
- Path-traversal rejection, so people cannot trick your server into reading files it should not.
- Standardized, RFC 9457 error responses that redact sensitive details in production.
There is also first-party middleware for the usual suspects: secure HTTP headers, CORS, CSRF, rate limiting, and request ids. You add them with one line each:
app.use(requestId());
app.use(secureHeaders());
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
The framework even hardens the install side of things, with defaults that wait 24 hours before pulling freshly published packages and block sketchy lifecycle scripts. You do not need to understand all of that yet. Just know that someone thought about it so you do not get burned while you are still learning.
So when would you reach for it?
If you are building a REST API in TypeScript and you want:
- Docs that stay correct without extra effort,
- Validation and types from one definition,
- A typed client for your frontend,
- Sane security defaults,
- The freedom to deploy to Node now and the edge later,
then DaloyJS is worth a try. It is genuinely beginner-friendly because it removes the busywork that usually trips up newer developers, while still being the kind of thing a senior team can run in production.
Try it in two minutes
The fastest way in is the official scaffolder:
pnpm create daloy@latest my-api
That gives you a working project with docs routes, OpenAPI wiring, and production defaults already set up. Open it, poke at the example route, change a field, and watch the docs update. That little feedback loop is, honestly, the whole pitch.
I spent years duct-taping docs, validation, and types together by hand. If you are just starting out, you get to skip that phase entirely. Lucky you.