T

Typed Routes

Type-safe Express routes with properly typed request parameters, body, and response.

Code

typescript
1import express, {
2 Request,
3 Response,
4 NextFunction,
5 Router,
6 RequestHandler,
7} from "express";
8
9// Type definitions
10interface User {
11 id: number;
12 name: string;
13 email: string;
14 role: "admin" | "user";
15 createdAt: Date;
16}
17
18// Typed request interfaces
19interface TypedRequestBody<T> extends Request {
20 body: T;
21}
22
23interface TypedRequestParams<T extends Record<string, string>> extends Request {
24 params: T;
25}
26
27interface TypedRequestQuery<T> extends Request {
28 query: T;
29}
30
31// Combined typed request
32interface TypedRequest<
33 TParams extends Record<string, string> = Record<string, string>,
34 TBody = unknown,
35 TQuery = unknown
36> extends Request {
37 params: TParams;
38 body: TBody;
39 query: TQuery;
40}
41
42// Response type helper
43type TypedResponse<T> = Response<T>;
44
45// DTO types
46interface CreateUserDto {
47 name: string;
48 email: string;
49 role?: "admin" | "user";
50}
51
52interface UpdateUserDto {
53 name?: string;
54 email?: string;
55 role?: "admin" | "user";
56}
57
58interface UserParams {
59 id: string;
60}
61
62interface UserQuery {
63 page?: string;
64 limit?: string;
65 role?: "admin" | "user";
66}
67
68// In-memory database
69const users: User[] = [];
70let nextId = 1;
71
72// Typed route handlers
73const getUsers: RequestHandler<
74 Record<string, string>,
75 User[],
76 unknown,
77 UserQuery
78> = (req, res) => {
79 const { page = "1", limit = "10", role } = req.query;
80 let result = users;
81
82 if (role) {
83 result = result.filter((u) => u.role === role);
84 }
85
86 const start = (parseInt(page) - 1) * parseInt(limit);
87 const end = start + parseInt(limit);
88
89 res.json(result.slice(start, end));
90};
91
92const getUserById: RequestHandler<UserParams, User | { error: string }> = (
93 req,
94 res
95) => {
96 const user = users.find((u) => u.id === parseInt(req.params.id));
97
98 if (!user) {
99 res.status(404).json({ error: "User not found" });
100 return;
101 }
102
103 res.json(user);
104};
105
106const createUser: RequestHandler<
107 Record<string, string>,
108 User | { error: string },
109 CreateUserDto
110> = (req, res) => {
111 const { name, email, role = "user" } = req.body;
112
113 // Validation
114 if (!name || !email) {
115 res.status(400).json({ error: "Name and email are required" });
116 return;
117 }
118
119 const newUser: User = {
120 id: nextId++,
121 name,
122 email,
123 role,
124 createdAt: new Date(),
125 };
126
127 users.push(newUser);
128 res.status(201).json(newUser);
129};
130
131const updateUser: RequestHandler<
132 UserParams,
133 User | { error: string },
134 UpdateUserDto
135> = (req, res) => {
136 const id = parseInt(req.params.id);
137 const userIndex = users.findIndex((u) => u.id === id);
138
139 if (userIndex === -1) {
140 res.status(404).json({ error: "User not found" });
141 return;
142 }
143
144 users[userIndex] = { ...users[userIndex], ...req.body };
145 res.json(users[userIndex]);
146};
147
148const deleteUser: RequestHandler<UserParams, void | { error: string }> = (
149 req,
150 res
151) => {
152 const id = parseInt(req.params.id);
153 const userIndex = users.findIndex((u) => u.id === id);
154
155 if (userIndex === -1) {
156 res.status(404).json({ error: "User not found" });
157 return;
158 }
159
160 users.splice(userIndex, 1);
161 res.status(204).send();
162};
163
164// Router setup
165const router = Router();
166
167router.get("/", getUsers);
168router.get("/:id", getUserById);
169router.post("/", createUser);
170router.patch("/:id", updateUser);
171router.delete("/:id", deleteUser);
172
173// App setup
174const app = express();
175app.use(express.json());
176app.use("/api/users", router);
177
178app.listen(3000, () => {
179 console.log("Server running on http://localhost:3000");
180});
181
182export { app, router };

Run this example locally

$ npm install express @types/express