Express + TypeScript:Request/Response 類型
Express 是 Node.js 最流行的 Web 框架。本篇將介紹如何為 Express 加上完整的類型定義。
一、 基本設定
1.1 安裝依賴
bash
npm install express
npm install -D typescript @types/express @types/node tsx1.2 基本伺服器
typescript
// src/index.ts
import express from 'express';
const app = express();
const port = process.env.PORT ?? 3000;
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: 'Hello, World!' });
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});二、 Request 類型
2.1 基本結構
Express 的 Request 有四個泛型參數:
typescript
interface Request<
P = ParamsDictionary, // 路由參數
ResBody = any, // 回應 body(較少用)
ReqBody = any, // 請求 body
ReqQuery = ParsedQs // 查詢參數
> {}2.2 路由參數
typescript
import { Request, Response } from 'express';
interface UserParams {
id: string;
}
app.get('/users/:id', (req: Request<UserParams>, res: Response) => {
const { id } = req.params; // id: string
res.json({ userId: id });
});2.3 請求 Body
typescript
interface CreateUserBody {
name: string;
email: string;
password: string;
}
app.post('/users', (req: Request<{}, {}, CreateUserBody>, res: Response) => {
const { name, email, password } = req.body;
// name: string, email: string, password: string
res.status(201).json({ message: 'User created' });
});2.4 查詢參數
typescript
interface SearchQuery {
keyword?: string;
page?: string;
limit?: string;
}
app.get('/search', (req: Request<{}, {}, {}, SearchQuery>, res: Response) => {
const { keyword, page, limit } = req.query;
// 都是 string | undefined
const pageNum = parseInt(page ?? '1', 10);
const limitNum = parseInt(limit ?? '10', 10);
res.json({ keyword, page: pageNum, limit: limitNum });
});三、 Response 類型
3.1 定義回應類型
typescript
interface UserResponse {
id: number;
name: string;
email: string;
}
app.get(
'/users/:id',
(req: Request<{ id: string }>, res: Response<UserResponse>) => {
const user: UserResponse = {
id: parseInt(req.params.id, 10),
name: 'John',
email: 'john@example.com',
};
res.json(user); // 類型安全
}
);3.2 統一回應格式
typescript
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
type UserListResponse = ApiResponse<UserResponse[]>;
app.get('/users', (req: Request, res: Response<UserListResponse>) => {
const users: UserResponse[] = [
{ id: 1, name: 'John', email: 'john@example.com' },
];
res.json({
success: true,
data: users,
});
});四、 自訂 Request 擴展
4.1 擴展 Request
typescript
// src/types/express.d.ts
import { User } from './user';
declare global {
namespace Express {
interface Request {
user?: User;
requestId?: string;
}
}
}
export {};4.2 使用擴展
typescript
// 認證中介軟體
app.use((req, res, next) => {
req.user = { id: 1, name: 'John' };
req.requestId = crypto.randomUUID();
next();
});
// 路由中使用
app.get('/me', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json(req.user); // User 類型
});五、 中介軟體類型
5.1 基本中介軟體
typescript
import { Request, Response, NextFunction } from 'express';
function logger(req: Request, res: Response, next: NextFunction): void {
console.log(`${req.method} ${req.path}`);
next();
}
app.use(logger);5.2 非同步中介軟體
typescript
import { Request, Response, NextFunction } from 'express';
type AsyncHandler = (
req: Request,
res: Response,
next: NextFunction
) => Promise<void>;
function asyncHandler(fn: AsyncHandler) {
return (req: Request, res: Response, next: NextFunction) => {
fn(req, res, next).catch(next);
};
}
// 使用
app.get(
'/async',
asyncHandler(async (req, res) => {
const data = await fetchData();
res.json(data);
})
);5.3 錯誤處理中介軟體
typescript
import { Request, Response, NextFunction } from 'express';
interface ApiError extends Error {
statusCode?: number;
}
function errorHandler(
err: ApiError,
req: Request,
res: Response,
next: NextFunction
): void {
const statusCode = err.statusCode ?? 500;
const message = err.message ?? 'Internal Server Error';
res.status(statusCode).json({
success: false,
error: message,
});
}
app.use(errorHandler);六、 路由器類型
6.1 定義路由器
typescript
// src/routes/users.ts
import { Router, Request, Response } from 'express';
const router = Router();
interface UserParams {
id: string;
}
interface CreateUserBody {
name: string;
email: string;
}
router.get('/', (req: Request, res: Response) => {
res.json({ users: [] });
});
router.get('/:id', (req: Request<UserParams>, res: Response) => {
res.json({ id: req.params.id });
});
router.post('/', (req: Request<{}, {}, CreateUserBody>, res: Response) => {
res.status(201).json({ message: 'Created' });
});
export default router;6.2 使用路由器
typescript
// src/index.ts
import express from 'express';
import userRoutes from './routes/users';
const app = express();
app.use(express.json());
app.use('/api/users', userRoutes);七、 控制器模式
7.1 定義控制器
typescript
// src/controllers/userController.ts
import { Request, Response, NextFunction } from 'express';
interface UserParams {
id: string;
}
interface CreateUserBody {
name: string;
email: string;
}
export const userController = {
getAll: async (req: Request, res: Response, next: NextFunction) => {
try {
const users = await userService.findAll();
res.json({ success: true, data: users });
} catch (error) {
next(error);
}
},
getById: async (
req: Request<UserParams>,
res: Response,
next: NextFunction
) => {
try {
const user = await userService.findById(req.params.id);
if (!user) {
return res
.status(404)
.json({ success: false, error: 'User not found' });
}
res.json({ success: true, data: user });
} catch (error) {
next(error);
}
},
create: async (
req: Request<{}, {}, CreateUserBody>,
res: Response,
next: NextFunction
) => {
try {
const user = await userService.create(req.body);
res.status(201).json({ success: true, data: user });
} catch (error) {
next(error);
}
},
};7.2 使用控制器
typescript
// src/routes/users.ts
import { Router } from 'express';
import { userController } from '../controllers/userController';
const router = Router();
router.get('/', userController.getAll);
router.get('/:id', userController.getById);
router.post('/', userController.create);
export default router;八、 請求處理器類型
8.1 定義通用類型
typescript
// src/types/express.ts
import { Request, Response, NextFunction, RequestHandler } from 'express';
export type TypedRequestHandler<
P = {},
ResBody = any,
ReqBody = {},
ReqQuery = {}
> = (
req: Request<P, ResBody, ReqBody, ReqQuery>,
res: Response<ResBody>,
next: NextFunction
) => void | Promise<void>;8.2 使用
typescript
import { TypedRequestHandler } from '../types/express';
interface Params {
id: string;
}
interface Body {
name: string;
}
interface Query {
include?: string;
}
interface ResponseData {
user: User;
}
const getUser: TypedRequestHandler<Params, ResponseData, {}, Query> = async (
req,
res
) => {
const { id } = req.params;
const { include } = req.query;
const user = await findUser(id, include);
res.json({ user });
};總結
| 類型 | 用途 | 範例 |
|---|---|---|
Request<P> | 路由參數 | /users/:id |
Request<{}, {}, B> | 請求 Body | POST Body |
Request<{}, {}, {}, Q> | 查詢參數 | ?page=1 |
Response<T> | 回應類型 | res.json() |
> **最佳實踐**:
- 為每個路由定義參數介面
- 使用控制器模式分離邏輯
- 統一錯誤處理
進階挑戰
- 建立一個完整的 CRUD(Create Read Update Delete) API
- 實作 JWT(JSON Web Token)認證中介軟體並擴展 Request
- 建立一個類型安全的錯誤處理系統