- Type every route handler with `Request<Params, ResBody, ReqBody, Query>` generics: `router.get<{ id: string }>('/:id', (req, res) => { ... })`.
- Install `@types/express` and use `RequestHandler<P, ResBody, ReqBody>` for typed middleware signatures.
- Validate request bodies with Zod and infer types: `type Body = z.infer<typeof schema>` — pass the inferred type as the `ReqBody` generic.
- Create typed error handlers with `ErrorRequestHandler<Params, ResBody, ReqBody>` and chain them with `app.use()` — always type the `err` parameter.
- Define typed error classes extending `Error` with a `statusCode` property and use a centralized error handler typed as `ErrorRequestHandler`.
- Create a generic async wrapper: `const asyncHandler = <P, Res, Req>(fn: RequestHandler<P, Res, Req>): RequestHandler<P, Res, Req> => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next)`.
- Use declaration merging to extend Express types: `declare module 'express' { interface Request { user?: User } }` for auth middleware.
- Type response payloads with `res.json()` by setting the `ResBody` generic to ensure API contracts are enforced at compile time.
- Group route files by domain and type each router's param interfaces in a co-located `types.ts`.