본문으로 건너뛰기
Advertisement

13.3 Fastify + TypeScript — 스키마 기반 타입 추론

Fastify 소개

Fastify는 Express보다 훨씬 빠른 Node.js 웹 프레임워크로, JSON 스키마 기반 타입 추론이 특징입니다. TypeScript와의 통합이 매우 뛰어납니다.

npm install fastify
npm install --save-dev @types/node typescript

기본 설정

// src/index.ts
import Fastify from 'fastify'

const fastify = Fastify({
logger: true, // 내장 Pino 로거
})

// 플러그인 등록
await fastify.register(import('./plugins/auth'))
await fastify.register(import('./routes/user'), { prefix: '/api/users' })

await fastify.listen({ port: 3000 })

스키마 기반 타입 추론

Fastify는 JSON Schema로 요청/응답 타입을 정의하면 TypeScript 타입이 자동으로 추론됩니다.

import { FastifyPluginAsync } from 'fastify'

interface UserParams {
id: string
}

interface CreateUserBody {
name: string
email: string
age: number
}

interface UserResponse {
id: string
name: string
email: string
createdAt: string
}

const userRoutes: FastifyPluginAsync = async (fastify) => {
// JSON Schema + TypeScript 타입 동시 사용
fastify.get<{
Params: UserParams
Reply: UserResponse
}>(
'/:id',
{
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' },
},
required: ['id'],
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' },
createdAt: { type: 'string', format: 'date-time' },
},
},
},
},
},
async (request, reply) => {
const { id } = request.params // UserParams 타입 자동 적용
const user = await getUserById(id)
return reply.send(user)
}
)

fastify.post<{
Body: CreateUserBody
Reply: { 201: UserResponse }
}>(
'/',
{
schema: {
body: {
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
age: { type: 'number', minimum: 0 },
},
required: ['name', 'email', 'age'],
},
},
},
async (request, reply) => {
const { name, email, age } = request.body // CreateUserBody 타입
const user = await createUser({ name, email, age })
return reply.status(201).send(user)
}
)
}

export default userRoutes

TypeBox로 타입과 스키마 통합

TypeBox는 JSON Schema와 TypeScript 타입을 동시에 생성합니다.

npm install @sinclair/typebox
import { Type, Static } from '@sinclair/typebox'
import { FastifyPluginAsync } from 'fastify'

// Schema와 TypeScript 타입 동시 정의
const CreateUserSchema = Type.Object({
name: Type.String({ minLength: 1, maxLength: 100 }),
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
role: Type.Optional(Type.Union([
Type.Literal('admin'),
Type.Literal('user'),
])),
})

const UserResponseSchema = Type.Object({
id: Type.String(),
name: Type.String(),
email: Type.String(),
age: Type.Integer(),
role: Type.String(),
createdAt: Type.String({ format: 'date-time' }),
})

// TypeScript 타입 추출
type CreateUserInput = Static<typeof CreateUserSchema>
type UserResponse = Static<typeof UserResponseSchema>

const userRoutes: FastifyPluginAsync = async (fastify) => {
fastify.post<{
Body: CreateUserInput
Reply: UserResponse
}>(
'/',
{
schema: {
body: CreateUserSchema,
response: { 201: UserResponseSchema },
},
},
async (request, reply) => {
// request.body: CreateUserInput (타입 안전)
const user = await createUser(request.body)
return reply.status(201).send(user)
}
)
}

플러그인 타입 시스템

Fastify의 플러그인은 fastify-plugin과 함께 인스턴스를 장식(decorate)합니다.

// plugins/auth.ts
import fp from 'fastify-plugin'
import { FastifyPluginAsync, FastifyRequest } from 'fastify'
import jwt from '@fastify/jwt'

// 플러그인이 추가하는 타입 선언
declare module '@fastify/jwt' {
interface FastifyJWT {
payload: { userId: string; role: string }
user: { id: string; email: string; role: string }
}
}

const authPlugin: FastifyPluginAsync = async (fastify) => {
// JWT 플러그인 등록
await fastify.register(jwt, {
secret: process.env.JWT_SECRET!,
})

// 커스텀 데코레이터 추가
fastify.decorate('authenticate', async (request: FastifyRequest) => {
try {
await request.jwtVerify()
} catch (err) {
throw fastify.httpErrors.unauthorized('인증이 필요합니다.')
}
})
}

// fp()로 감싸면 플러그인 스코프가 부모로 확장됨
export default fp(authPlugin)

// Fastify 인스턴스 타입 확장
declare module 'fastify' {
interface FastifyInstance {
authenticate: (request: FastifyRequest) => Promise<void>
}
}

preHandler로 라우트 보호

fastify.get(
'/protected',
{
preHandler: [fastify.authenticate], // 인증 검사
},
async (request, reply) => {
// request.user: { id, email, role } (타입 안전)
return { user: request.user }
}
)

고수 팁

1. 에러 처리

fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
return reply.status(422).send({
error: '유효성 검사 실패',
details: error.validation,
})
}

fastify.log.error(error)
return reply.status(500).send({ error: '서버 오류' })
})

2. 직렬화 성능

// fast-json-stringify 내장 — 응답 스키마 정의 시 직렬화 3x 빨라짐
{
schema: {
response: {
200: UserResponseSchema // 스키마 있으면 자동 최적화
}
}
}
Advertisement