Getting started
Build a tiny DaloyJS server, hit it with the typed client, and inspect the OpenAPI spec — in five minutes.
1. Scaffold
mkdir hello-daloy && cd hello-daloy
pnpm init
pnpm add @daloyjs/core zod
pnpm add -D typescript tsx @types/node// package.json — add these
{
"type": "module",
"scripts": {
"dev": "node --import tsx/esm --watch src/index.ts",
"start": "node --import tsx/esm src/index.ts"
}
}We use the explicit tsx/esm loader subpath because the project is "type": "module". The bare --import tsx form also works on recent Node versions, but tsx/esm is the canonical entrypoint for ESM projects and avoids loader-resolution surprises in stricter setups.
We use src/index.ts and --watch here so the layout matches what create-daloy emits — copy/paste between this guide and a scaffolded project without renaming files.
2. Write your first route
// src/index.ts
import { z } from "zod";
import { App, requestId, secureHeaders } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
const app = new App({
bodyLimitBytes: 64 * 1024,
requestTimeoutMs: 5_000,
});
app.use(requestId());
app.use(secureHeaders());
app.route({
method: "GET",
path: "/greet/:name",
operationId: "greet",
tags: ["Demo"],
request: { params: z.object({ name: z.string().min(1) }) },
responses: {
200: { description: "Greeting", body: z.object({ msg: z.string() }) },
},
handler: async ({ params }) => ({
status: 200,
body: { msg: `Hello, ${params.name}!` },
}),
});
const { port } = serve(app, { port: 3000 });
console.log(`listening on http://localhost:${port}`);Prefer the colorized startup panel you get from create-daloy templates? Swap the plain console.log for printStartupBanner() from @daloyjs/core/banner — it renders a TTY-aware, ASCII-fallback boxed banner with your app name, URL, and any extra links (Swagger UI, health check, etc.):
import { printStartupBanner } from "@daloyjs/core/banner";
const { port } = serve(app, { port: 3000 });
printStartupBanner({
name: "MyAPI",
version: "1.0.0",
url: `http://localhost:${port}`,
runtime: "Node.js",
links: [
{ label: "Swagger UI", url: `http://localhost:${port}/docs` },
{ label: "OpenAPI JSON", url: `http://localhost:${port}/openapi.json` },
{ label: "Health", url: `http://localhost:${port}/healthz` },
],
});pnpm dev
# in another shell
curl http://localhost:3000/greet/world
# → {"msg":"Hello, world!"}Don't want to spin up a real server? Every App exposes app.request(input, init?), an in-process test client that takes a URL or Request and returns a Response— no network stack, no port, no second terminal. It's the same entrypoint the typed client and testing guide use:
const res = await app.request("/greet/world");
console.log(res.status, await res.json());
// → 200 { msg: "Hello, world!" }3. Add OpenAPI & docs UI
import { generateOpenAPI } from "@daloyjs/core/openapi";
import { swaggerUiHtml, htmlResponse } from "@daloyjs/core/docs";
app.route({
method: "GET",
path: "/openapi.json",
operationId: "openapi",
responses: { 200: { description: "OpenAPI doc" } },
handler: async () => ({
status: 200,
body: generateOpenAPI(app, { info: { title: "Hello", version: "1.0.0" } }),
}),
});
app.route({
method: "GET",
path: "/docs",
operationId: "docs",
responses: { 200: { description: "API reference" } },
handler: async () => {
const html = swaggerUiHtml({ specUrl: "/openapi.json", title: "Hello API" });
const res = htmlResponse(html);
return { status: 200, body: await res.text(), headers: Object.fromEntries(res.headers) };
},
});Open http://localhost:3000/docs for interactive Swagger UI.
Both swaggerUiHtml() and scalarHtml() load their default assets from the jsDelivr CDN, so a strict Content-Security-Policy must allow those assets or the docs UI can render blank. htmlResponse() adds a compatible CSP automatically; if you build your own response, import docsContentSecurityPolicy from @daloyjs/core/docs and pass the result as the response header:
import { docsContentSecurityPolicy } from "@daloyjs/core/docs";
headers: { "content-security-policy": docsContentSecurityPolicy() }4. Use the typed in-process client
import { createClient } from "@daloyjs/core/client";
const client = createClient(app, { baseUrl: "http://localhost:3000" });
const r = await client.greet({ params: { name: "DaloyJS" } });
// ^? { status: 200; body: { msg: string } }
console.log(r.status, r.body);5. Generate a Hey API SDK
For consumers outside the monorepo, generate a fully typed fetch SDK:
pnpm add -D @hey-api/openapi-ts// openapi-ts.config.ts
import { defineConfig } from "@hey-api/openapi-ts";
export default defineConfig({
input: "./generated/openapi.json",
output: { path: "./generated/client", format: "prettier" },
plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
});pnpm exec openapi-ts