Migrating a backend
Nifra's server() is a chainable, Web-standard router — the move from Express, Hono, Fastify, or Elysia is mostly mechanical renames. What you gain: an end-to-end-typed client with zero codegen, schema validation built into the route, the same app on Bun / Node / Deno / the edge, and an SSR frontend whenever you want one.
How the concepts map
| Express / Hono / Fastify | Nifra |
|---|---|
| `app.get(path, handler)` | server().get(path, handler) — chainable, fully type-inferred |
| `req` / `res` · Hono `Context` | c — `c.req`, `c.params`, `c.query`; return a value (JSON) or a Response |
| `app.use(mw)` · Hono middleware | .use() + the @nifrajs/middleware pack (CORS, auth, rate limit, …) |
| `express.json()` / body parsing | built in — declare a schema and the parsed body is typed |
| Fastify JSON schema · `zod` middleware · Elysia `t` | { body, query, params } validated by t (TypeBox) or any Standard Schema |
| `res.json(x)` / `reply.send(x)` / `c.json(x)` | return x |
| `app.listen(3000)` | app.listen() (Bun) · @nifrajs/node · @nifrajs/deno · toFetchHandler (edge) |
| OpenAPI plugins / swagger | generated from the same `t` schemas — no second source of truth |
Express → Nifra
The handler returns its result instead of calling res.json, and validation moves onto the route. The client then infers all of this for free.
// Express
app.get("/users/:id", (req, res) => res.json({ id: req.params.id }))
app.post("/users", express.json(), (req, res) => res.status(201).json(create(req.body)))
// nifra — return the value (or a Response); c is Web-standard; the body is parsed + validated
server()
.get("/users/:id", (c) => ({ id: c.params.id }))
.post("/users", { body: t.object({ name: t.string() }) }, (c) => create(c.body))Hono → Nifra
The closest of the routers — c.req.param("id") becomes a typed c.params.id, and you return the value rather than c.json(...). Nifra adds the typed client, schema validation, multi-runtime deploy, and (if you want it) SSR.
// Hono
app.get("/users/:id", (c) => c.json({ id: c.req.param("id") }))
// nifra — nearly identical; params are a typed object, you return the value
server().get("/users/:id", (c) => ({ id: c.params.id }))Fastify & Elysia
- Fastify — its per-route JSON-schema validation maps directly to Nifra's
{ body, query, params }; plugins become Nifra plugins;reply.sendbecomes a return. - Elysia — the closest peer (Bun, typed, TypeBox
t). The shapes are nearly 1:1:.get/.post+ atschema, and an Eden-style typed client. You gain Node / Deno / edge portability and an optional SSR frontend on five UI libraries.
Then connect a database (Database) and, if you're going full-stack, see Migrating a meta-framework.