跳至主要內容
Skip to content

Express + TypeScript:Request/Response 類型

Express 是 Node.js 最流行的 Web 框架。本篇將介紹如何為 Express 加上完整的類型定義。


一、 基本設定

1.1 安裝依賴

bash
npm install express
npm install -D typescript @types/express @types/node tsx

1.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>請求 BodyPOST Body
Request<{}, {}, {}, Q>查詢參數?page=1
Response<T>回應類型res.json()

> **最佳實踐**:

  • 為每個路由定義參數介面
  • 使用控制器模式分離邏輯
  • 統一錯誤處理

進階挑戰

  1. 建立一個完整的 CRUD(Create Read Update Delete) API
  2. 實作 JWT(JSON Web Token)認證中介軟體並擴展 Request
  3. 建立一個類型安全的錯誤處理系統

延伸閱讀與資源