Express, Hono, Elysia, NestJS, tRPC, oRPC & Bun β routes, middleware, type safety & patterns
Framework / Backend// npm i express && npm i -D @types/express tsx
import express, { Request, Response, NextFunction } from "express";
const app = express();
app.use(express.json());
// Typed route handler
interface User { id: number; name: string; email: string; }
app.get("/users", (req: Request, res: Response) => {
const page = Number(req.query.page) || 1;
res.json({ users: [], page });
});
app.post("/users", (req: Request<{}, {}, Omit<User, "id">>, res) => {
const { name, email } = req.body;
res.status(201).json({ id: 1, name, email });
});
app.get("/users/:id", (req: Request<{ id: string }>, res) => {
const id = parseInt(req.params.id);
res.json({ id });
});
// Middleware
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: "Unauthorized" });
next();
};
app.use("/api", authMiddleware);
// Error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
res.status(500).json({ error: err.message });
});
app.listen(3000, () => console.log("Express on :3000"));// npm i hono | Works on Node, Bun, Deno, CF Workers, Vercel Edge
import { Hono } from "hono";
import { cors } from "hono/cors";
import { jwt } from "hono/jwt";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono();
// Middleware
app.use("*", cors());
app.use("/api/*", jwt({ secret: "my-secret" }));
// Routes with type-safe validation
const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
app.get("/users", (c) => {
const page = c.req.query("page") || "1";
return c.json({ users: [], page: Number(page) });
});
app.post("/users", zValidator("json", createUserSchema), (c) => {
const data = c.req.valid("json"); // fully typed!
return c.json({ id: 1, ...data }, 201);
});
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id });
});
// Route groups
const api = new Hono();
api.get("/health", (c) => c.text("OK"));
app.route("/api", api);
// Error handling
app.onError((err, c) => {
return c.json({ error: err.message }, 500);
});
export default app; // for Bun/CF Workers
// Or: serve({ fetch: app.fetch, port: 3000 })// bun add elysia @elysiajs/swagger
import { Elysia, t } from "elysia";
import { swagger } from "@elysiajs/swagger";
const app = new Elysia()
.use(swagger())
// Type-safe route with validation
.get("/users", ({ query }) => {
return { users: [], page: query.page };
}, {
query: t.Object({ page: t.Number({ default: 1 }) })
})
.post("/users", ({ body }) => {
return { id: 1, ...body };
}, {
body: t.Object({
name: t.String({ minLength: 2 }),
email: t.String({ format: "email" }),
}),
response: t.Object({
id: t.Number(),
name: t.String(),
email: t.String(),
}),
})
.get("/users/:id", ({ params }) => {
return { id: params.id };
}, {
params: t.Object({ id: t.Number() })
})
// Middleware (derive/guard)
.derive(({ headers }) => {
const token = headers.authorization;
return { userId: verifyToken(token) };
})
// Groups
.group("/api", (app) =>
app.get("/health", () => "OK")
)
.listen(3000);
console.log(`Elysia running at ${app.server?.url}`);// npx @nestjs/cli new my-project
// Module + Controller + Service architecture
// ββ user.entity.ts ββ
export class User {
id: number;
name: string;
email: string;
}
// ββ create-user.dto.ts ββ
import { IsString, IsEmail, MinLength } from "class-validator";
export class CreateUserDto {
@IsString() @MinLength(2)
name: string;
@IsEmail()
email: string;
}
// ββ users.service.ts ββ
import { Injectable } from "@nestjs/common";
@Injectable()
export class UsersService {
private users: User[] = [];
findAll(): User[] { return this.users; }
findOne(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
create(dto: CreateUserDto): User {
const user = { id: Date.now(), ...dto };
this.users.push(user);
return user;
}
}
// ββ users.controller.ts ββ
import { Controller, Get, Post, Body, Param, UseGuards } from "@nestjs/common";
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() { return this.usersService.findAll(); }
@Get(":id")
findOne(@Param("id") id: string) {
return this.usersService.findOne(+id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
// ββ users.module.ts ββ
import { Module } from "@nestjs/common";
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
// Guards (auth)
@UseGuards(AuthGuard)
@Get("profile")
getProfile() { ... }// npm i @trpc/server @trpc/client zod
// ββ server/trpc.ts ββ (initialize)
import { initTRPC, TRPCError } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.context<{ userId?: string }>().create();
export const router = t.router;
export const publicProcedure = t.procedure;
// Protected procedure
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.userId) throw new TRPCError({ code: "UNAUTHORIZED" });
return next({ ctx: { userId: ctx.userId } });
});
// ββ server/routers/users.ts ββ
export const usersRouter = router({
list: publicProcedure.query(async () => {
return db.user.findMany();
}),
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
create: protectedProcedure
.input(z.object({
name: z.string().min(2),
email: z.string().email(),
}))
.mutation(async ({ input, ctx }) => {
return db.user.create({ data: { ...input, createdBy: ctx.userId } });
}),
});
// ββ server/index.ts ββ (root router)
export const appRouter = router({
users: usersRouter,
});
export type AppRouter = typeof appRouter;
// ββ client usage (React example) ββ
import { createTRPCReact } from "@trpc/react-query";
const trpc = createTRPCReact<AppRouter>();
// In component β fully type-safe!
const { data } = trpc.users.list.useQuery();
const mutation = trpc.users.create.useMutation();// npm i @orpc/server @orpc/client
// Type-safe RPC β framework-agnostic, OpenAPI-native
import { os } from "@orpc/server";
import { z } from "zod";
// Define procedures
const listUsers = os
.input(z.object({ page: z.number().optional() }))
.handler(async ({ input }) => {
return db.user.findMany({ skip: ((input.page ?? 1) - 1) * 20, take: 20 });
});
const createUser = os
.input(z.object({
name: z.string().min(2),
email: z.string().email(),
}))
.handler(async ({ input }) => {
return db.user.create({ data: input });
});
// Router
const appRouter = os.router({
users: os.router({
list: listUsers,
create: createUser,
}),
});
// Serve (works with any HTTP framework)
import { createServer } from "@orpc/server/node";
createServer({ router: appRouter }).listen(3000);
// Client
import { createORPCClient } from "@orpc/client";
const client = createORPCClient<typeof appRouter>({ baseURL: "http://localhost:3000" });
const users = await client.users.list({ page: 1 }); // typed!// Bun has a built-in HTTP server β no framework needed
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/users" && req.method === "GET") {
return Response.json({ users: [] });
}
if (url.pathname === "/users" && req.method === "POST") {
const body = await req.json();
return Response.json({ id: 1, ...body }, { status: 201 });
}
return new Response("Not Found", { status: 404 });
},
});
// Bun utilities
const file = Bun.file("data.json");
const text = await file.text();
await Bun.write("output.txt", "Hello");
// SQLite (built-in!)
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
const users = db.query("SELECT * FROM users").all();
// Password hashing (built-in!)
const hash = await Bun.password.hash("mypassword");
const valid = await Bun.password.verify("mypassword", hash);
// Package manager: bun install, bun add, bun remove
// Runner: bun run script.ts, bunx create-next-app| Framework | Runtime | Style | Best For |
|---|---|---|---|
| Express | Node | Minimal | Mature ecosystem, flexible |
| Hono | Any | Minimal | Edge/serverless, ultra-fast |
| Elysia | Bun | Minimal | Performance, end-to-end types |
| NestJS | Node | Opinionated | Enterprise, DI, Angular-like |
| tRPC | Any | RPC | Full-stack TS, type inference |
| oRPC | Any | RPC | OpenAPI-native, framework-agnostic |