Express.js routing, middleware, and error handling. Trigger: When building REST APIs or server logic with Express.
REST APIs and server logic with Express v4.x middleware and routing.
Don't use for:
Group routes into Router instances for clean app.ts.
// CORRECT: modular router in routes/users.ts
const router = Router();
router.get("/", listUsers);
router.post("/", createUser);
export default router;
// WRONG: all routes dumped directly in app.ts
Express 4 doesn't catch rejected promises. Wrap async handlers.
// CORRECT: wrapper forwards rejections to error middleware
const asyncHandler = (fn: RequestHandler): RequestHandler =>
(req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
router.get("/users/:id", asyncHandler(async (req, res) => {
const user = await db.findUser(req.params.id);
if (!user) throw new NotFoundError("User not found");
res.json(user);
}));
Single error handler as last middleware in stack.
// CORRECT: 4-argument signature signals error middleware
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
const status = err.statusCode ?? 500;
res.status(status).json({ error: err.message });
};
app.use(errorHandler);
// WRONG: try/catch in every route returning res.status(500)
Validate bodies before route logic.
const CreateUser = z.object({ name: z.string(), email: z.string().email() });
const validate = (schema: z.ZodSchema): RequestHandler =>
(req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) return res.status(400).json(result.error.flatten());
req.body = result.data;
next();
};
Return correct HTTP codes per operation.
// CORRECT: 201 for creation, 204 for no-content delete
router.post("/users", asyncHandler(async (req, res) => {
const user = await db.createUser(req.body);
res.status(201).json(user);
}));
router.delete("/users/:id", asyncHandler(async (req, res) => {
await db.deleteUser(req.params.id);
res.status(204).end();
}));
Multiple resource routes?
→ Group into a Router per resource
Async handler?
→ Wrap with asyncHandler utility
Need auth?
→ Add auth middleware before route handlers
Input from client?
→ Validate with schema middleware
Creating a resource?
→ Return 201, not 200
Deleting a resource?
→ Return 204 with no body
Unhandled error?
→ Let centralized error middleware respond
import express from "express";
import { z } from "zod";
const app = express();
app.use(express.json());
const asyncHandler = (fn: Function) =>
(req: any, res: any, next: any) => Promise.resolve(fn(req, res, next)).catch(next);
const UserSchema = z.object({ name: z.string(), email: z.string().email() });
app.get("/users/:id", asyncHandler(async (req: any, res: any) => {
const user = await db.findUser(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
}));
app.post("/users", asyncHandler(async (req: any, res: any) => {
const data = UserSchema.parse(req.body);
res.status(201).json(await db.createUser(data));
}));
express.json({ limit: "1mb" }) to prevent 413/memory issues.res.json() twice throws; guard with if (res.headersSent).Content-Type: application/json → empty req.body.app.use callexpress.json() has an explicit size limitSearch for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer