In the ground since Thu Dec 19 2024
Last watered inThu Dec 19 2024
Typing Express Routes - Quick Guide
Overview
Express.js routes can be strongly typed using TypeScript generics to ensure type safety for request parameters, response bodies, request bodies, and query parameters.
Basic Express Request Type Structure
1Request<Params, ResBody, ReqBody, ReqQuery, Locals>;
- Params: Route parameters (e.g., /:id)
- ResBody: Response body type
- ReqBody: Request body type
- ReqQuery: Query string parameters
- Locals: Local variables in res.locals
Common Patterns
1. Routes Without Parameters
1// POST /tournaments
2export type CreateTournamentRequest = Request<null, CreateTournamentInput>;
3
4// Usage in handler
5static async createTournament(req: CreateTournamentRequest, res: Response) {
6 const { label, mode } = req.body; // Typed as CreateTournamentInput
7}
2. Routes With Parameters
1// GET /tournaments/:tournamentId
2export type GetTournamentRequest = Request<{ tournamentId: string }>;
3
4// Usage in handler
5static async getTournament(req: GetTournamentRequest, res: Response) {
6 const { tournamentId } = req.params; // Typed as string
7}
3. Flexible Routes (Parameters Optional)
1// Handles both POST /tournaments and GET /tournaments/:tournamentId
2export type TournamentRequest = Request<{ tournamentId?: string }, CreateTournamentInput>;
3
4// Usage
5static async handleTournament(req: TournamentRequest, res: Response) {
6 const tournamentId = req.params.tournamentId; // string | undefined
7 const body = req.body; // CreateTournamentInput
8}
4. Complex Routes with Multiple Parameters
1// GET /tournaments/:tournamentId/rounds/:roundId
2export type TournamentRoundRequest = Request<{
3 tournamentId: string;
4 roundId: string;
5}>;
5. Routes with Query Parameters
1// GET /tournaments?page=1&limit=10
2export type TournamentListRequest = Request<
3 null, // No route params
4 unknown, // Response body (handled separately)
5 unknown, // Request body
6 { page?: string; limit?: string } // Query params
7>;
Best Practices
1. Create Specific Types for Each Route Pattern
1// Good: Specific types
2export type CreateTournamentRequest = Request<null, CreateTournamentInput>;
3export type UpdateTournamentRequest = Request<{ tournamentId: string }, UpdateTournamentInput>;
4
5// Avoid: Generic any types
6export type GenericRequest = Request<any, any>;
2. Use Optional Parameters for Flexible Routes
1// Handles multiple route patterns
2export type TournamentRequest = Request<
3 {
4 tournamentId?: string;
5 roundId?: string;
6 },
7 TournamentInput
8>;
3. Group Related Types
1// Tournament-related types
2export namespace TournamentTypes {
3 export type CreateRequest = Request<null, CreateTournamentInput>;
4 export type GetRequest = Request<{ tournamentId: string }>;
5 export type UpdateRequest = Request<{ tournamentId: string }, UpdateTournamentInput>;
6}
4. Extend Base Types When Needed
1// Base authenticated request
2interface AuthenticatedRequest<P = null, B = unknown> extends Request<P, unknown, B> {
3 authenticatedUser?: User;
4}
5
6// Specific tournament request with auth
7export type AuthenticatedTournamentRequest = AuthenticatedRequest<{ tournamentId: string }, CreateTournamentInput>;
Common Pitfalls
❌ Don't use any
1// Bad
2export type BadRequest = Request<any, any>;
❌ Don't forget optional parameters
1// Bad: Will break on routes without tournamentId
2export type BadTournamentRequest = Request<{ tournamentId: string }>;
3
4// Good: Works for both parameterized and non-parameterized routes
5export type GoodTournamentRequest = Request<{ tournamentId?: string }>;
❌ Don't mix incompatible route patterns
1// Bad: tournamentId required but used on routes without it
2router.post('/tournaments', handler); // No tournamentId in route
3// Type expects tournamentId but route doesn't provide it
Example Implementation
1// types.ts
2export interface CreateTournamentInput {
3 label: string;
4 mode: string;
5}
6
7export type TournamentRequest = Request<{ tournamentId?: string }, CreateTournamentInput>;
8
9// routes.ts
10import { TournamentRequest } from './types';
11
12router.post('/tournaments', (req: TournamentRequest, res: Response) => {
13 // req.body is typed as CreateTournamentInput
14 // req.params.tournamentId is undefined
15});
16
17router.get('/tournaments/:tournamentId', (req: TournamentRequest, res: Response) => {
18 // req.params.tournamentId is string
19 // req.body is CreateTournamentInput (though not used in GET)
20});
This approach ensures type safety while maintaining flexibility across different route patterns! 🚀