Node.js + TypeScript 서버에서는 요청/응답 타입, 미들웨어, 라우트 매개변수를 타입 안전하게 정의할 수 있습니다.

프로젝트 설정

BASH
# 기본 패키지 설치
npm install express
npm install --save-dev typescript @types/node @types/express ts-node

# tsconfig.json 생성
npx tsc --init
JSON
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Express + TypeScript

기본 서버

TYPESCRIPT
import express, { Request, Response } from 'express';

const app = express();
app.use(express.json());

app.get('/', (req: Request, res: Response) => {
  res.json({ message: '안녕하세요' });
});

app.listen(3000, () => {
  console.log('서버 시작: http://localhost:3000');
});

요청/응답 타이핑

TYPESCRIPT
// 라우트 매개변수, 응답 본문, 요청 본문의 타입을 제네릭으로 지정
interface User {
  id: number;
  name: string;
  email: string;
}

type CreateUserBody = Omit<User, 'id'>;

// Request<Params, ResBody, ReqBody, Query>
app.post(
  '/users',
  (req: Request<{}, User, CreateUserBody>, res: Response<User>) => {
    const { name, email } = req.body; // 타입 안전
    const user: User = { id: Date.now(), name, email };
    res.json(user); // User 타입만 반환 가능
  }
);

// 라우트 매개변수 타이핑
app.get(
  '/users/:id',
  (req: Request<{ id: string }>, res: Response<User | { error: string }>) => {
    const { id } = req.params; // string
    // ...
  }
);

// 쿼리 파라미터 타이핑
app.get(
  '/search',
  (req: Request<{}, any, any, { q: string; page?: string }>, res: Response) => {
    const { q, page } = req.query;
    // q: string, page: string | undefined
  }
);

미들웨어 타이핑

TYPESCRIPT
import { NextFunction } from 'express';

// 인증 미들웨어
interface AuthRequest extends Request {
  user?: { id: number; role: string };
}

function authMiddleware(
  req: AuthRequest,
  res: Response,
  next: NextFunction
) {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: '인증 필요' });
  }

  // 토큰 검증 로직
  req.user = { id: 1, role: 'admin' };
  next();
}

app.get('/admin', authMiddleware, (req: AuthRequest, res: Response) => {
  console.log(req.user?.role); // string | undefined
});

에러 핸들링 미들웨어

TYPESCRIPT
// 에러 핸들링 미들웨어는 매개변수가 4개
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({ error: err.message });
});

Fastify + TypeScript

Fastify는 TypeScript를 1급으로 지원 하며, @types/ 패키지가 필요 없습니다.

BASH
npm install fastify
TYPESCRIPT
import Fastify from 'fastify';

const server = Fastify({ logger: true });

// 스키마 기반 타이핑
server.get<{
  Querystring: { page: string };
  Reply: { users: User[]; total: number };
}>('/users', async (request, reply) => {
  const { page } = request.query; // string
  const users = await getUsers(parseInt(page));
  return { users, total: users.length };
});

// 라우트 매개변수와 본문
server.post<{
  Params: { id: string };
  Body: { name: string; email: string };
  Reply: User;
}>('/users/:id', async (request, reply) => {
  const { id } = request.params;
  const { name, email } = request.body;
  // ...
});

server.listen({ port: 3000 });

Fastify + Zod

TYPESCRIPT
import { z } from 'zod';

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

server.post('/users', async (request, reply) => {
  const result = CreateUserSchema.safeParse(request.body);
  if (!result.success) {
    return reply.status(400).send({ error: result.error.issues });
  }
  const user = await createUser(result.data);
  return reply.status(201).send(user);
});

Express vs Fastify 타이핑 비교

특성ExpressFastify
타입 지원@types/express 필요내장
제네릭 파라미터Request<Params, Res, Req, Query>{ Params, Body, Query, Reply }
스키마 검증별도 미들웨어 필요JSON Schema 내장
타입 추론수동 타이핑스키마에서 자동 추론

정리

  • Express는 @types/express를 설치하고 Request<Params, Res, Req, Query> 제네릭으로 타이핑한다
  • Fastify는 TypeScript를 1급 지원하며 제네릭 라우트 정의가 직관적이다
  • 미들웨어에서 req를 확장하려면 인터페이스를 extends한다
  • Zod와 결합하면 런타임 검증과 타입 안전성을 동시에 확보할 수 있다
  • 새 프로젝트에서 TypeScript 지원이 중요하다면 Fastify를 고려하자
댓글 로딩 중...