728x90
반응형
1. Fastify란
Fastify는 Node.js를 위한 고성능 웹 프레임워크입니다. Express보다 빠르며, 스키마 기반 검증, 플러그인 아키텍처, 내장 로깅을 제공합니다. JSON 직렬화가 최적화되어 있어 API 서버에 적합합니다.
npm install fastify
const fastify = require('fastify')({ logger: true });
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
fastify.listen({ port: 3000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
});
2. 라우팅
2.1 기본 라우팅
const fastify = require('fastify')({ logger: true });
// GET
fastify.get('/', async (request, reply) => {
return { message: 'Hello' };
});
// POST
fastify.post('/users', async (request, reply) => {
const user = request.body;
reply.code(201);
return { created: user };
});
// PUT
fastify.put('/users/:id', async (request, reply) => {
const { id } = request.params;
return { updated: id };
});
// DELETE
fastify.delete('/users/:id', async (request, reply) => {
reply.code(204);
return;
});
// 모든 메서드
fastify.all('/all', async (request, reply) => {
return { method: request.method };
});
fastify.listen({ port: 3000 });
2.2 경로 파라미터
// 단일 파라미터
fastify.get('/users/:id', async (request, reply) => {
const { id } = request.params;
return { userId: id };
});
// 여러 파라미터
fastify.get('/users/:userId/posts/:postId', async (request, reply) => {
const { userId, postId } = request.params;
return { userId, postId };
});
// 와일드카드
fastify.get('/files/*', async (request, reply) => {
return { path: request.params['*'] };
});
// 정규식 파라미터
fastify.get('/items/:id(^\\d+)', async (request, reply) => {
return { id: request.params.id };
});
2.3 쿼리 파라미터
fastify.get('/search', async (request, reply) => {
const { q, page = 1, limit = 10 } = request.query;
return { query: q, page, limit };
});
3. 스키마 검증
Fastify는 JSON Schema를 사용한 빠른 검증을 제공합니다.
3.1 요청 검증
const userSchema = {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 2 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
}
},
params: {
type: 'object',
properties: {
id: { type: 'string', pattern: '^\\d+$' }
}
},
querystring: {
type: 'object',
properties: {
page: { type: 'integer', minimum: 1, default: 1 },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }
}
}
};
fastify.post('/users', { schema: userSchema }, async (request, reply) => {
// request.body는 이미 검증됨
return { created: request.body };
});
3.2 응답 스키마
응답 스키마를 정의하면 JSON 직렬화가 최적화됩니다.
const getUserSchema = {
response: {
200: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
createdAt: { type: 'string', format: 'date-time' }
}
},
404: {
type: 'object',
properties: {
error: { type: 'string' },
message: { type: 'string' }
}
}
}
};
fastify.get('/users/:id', { schema: getUserSchema }, async (request, reply) => {
const user = await findUser(request.params.id);
if (!user) {
reply.code(404);
return { error: 'Not Found', message: 'User not found' };
}
return user;
});
반응형
4. 훅 (Hooks)
Fastify는 요청 라이프사이클의 다양한 지점에 훅을 제공합니다.
// 요청 전 훅
fastify.addHook('onRequest', async (request, reply) => {
request.startTime = Date.now();
});
// 요청 본문 파싱 후
fastify.addHook('preHandler', async (request, reply) => {
// 인증 확인
if (!request.headers.authorization) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
// 직렬화 전
fastify.addHook('preSerialization', async (request, reply, payload) => {
return { ...payload, timestamp: new Date().toISOString() };
});
// 응답 전송 후
fastify.addHook('onResponse', async (request, reply) => {
const duration = Date.now() - request.startTime;
fastify.log.info(`${request.method} ${request.url} - ${duration}ms`);
});
// 에러 발생 시
fastify.addHook('onError', async (request, reply, error) => {
fastify.log.error(error);
});
5. 플러그인
5.1 플러그인 등록
// 플러그인 정의
async function myPlugin(fastify, options) {
fastify.decorate('myUtil', () => 'utility function');
fastify.addHook('onRequest', async (request) => {
request.customData = options.data;
});
}
// 등록
fastify.register(myPlugin, { data: 'custom' });
// 사용
fastify.get('/', async (request, reply) => {
return {
util: fastify.myUtil(),
data: request.customData
};
});
5.2 라우트 플러그인
// plugins/users.js
async function usersRoutes(fastify, options) {
const users = new Map();
let nextId = 1;
fastify.get('/', async (request, reply) => {
return Array.from(users.values());
});
fastify.get('/:id', async (request, reply) => {
const user = users.get(parseInt(request.params.id));
if (!user) {
reply.code(404);
return { error: 'User not found' };
}
return user;
});
fastify.post('/', async (request, reply) => {
const user = { id: nextId++, ...request.body };
users.set(user.id, user);
reply.code(201);
return user;
});
}
module.exports = usersRoutes;
// app.js
fastify.register(require('./plugins/users'), { prefix: '/api/users' });
5.3 공식 플러그인
// CORS
const cors = require('@fastify/cors');
fastify.register(cors, {
origin: ['http://localhost:3000'],
methods: ['GET', 'POST', 'PUT', 'DELETE']
});
// 정적 파일
const fastifyStatic = require('@fastify/static');
const path = require('path');
fastify.register(fastifyStatic, {
root: path.join(__dirname, 'public'),
prefix: '/public/'
});
// 쿠키
const cookie = require('@fastify/cookie');
fastify.register(cookie, {
secret: 'my-secret'
});
// JWT
const jwt = require('@fastify/jwt');
fastify.register(jwt, {
secret: 'supersecret'
});
fastify.post('/login', async (request, reply) => {
const token = fastify.jwt.sign({ userId: 1 });
return { token };
});
fastify.decorate('authenticate', async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
fastify.get('/protected', {
preHandler: [fastify.authenticate]
}, async (request, reply) => {
return request.user;
});
6. 에러 처리
// 전역 에러 핸들러
fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error);
if (error.validation) {
reply.status(400).send({
error: 'Validation Error',
message: error.message
});
return;
}
reply.status(error.statusCode || 500).send({
error: error.name || 'Internal Server Error',
message: error.message
});
});
// 404 핸들러
fastify.setNotFoundHandler((request, reply) => {
reply.code(404).send({
error: 'Not Found',
message: `Route ${request.method} ${request.url} not found`
});
});
// 커스텀 에러
class NotFoundError extends Error {
constructor(message) {
super(message);
this.statusCode = 404;
this.name = 'NotFoundError';
}
}
fastify.get('/users/:id', async (request, reply) => {
const user = await findUser(request.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
return user;
});
7. 데코레이터
// 인스턴스 데코레이터
fastify.decorate('config', {
appName: 'My App',
version: '1.0.0'
});
// 요청 데코레이터
fastify.decorateRequest('user', null);
fastify.addHook('preHandler', async (request, reply) => {
const token = request.headers.authorization;
if (token) {
request.user = await verifyToken(token);
}
});
// 응답 데코레이터
fastify.decorateReply('success', function(data) {
return this.send({ success: true, data });
});
fastify.get('/users', async (request, reply) => {
const users = await getUsers();
return reply.success(users);
});
8. 타입스크립트 지원
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
interface User {
id: number;
name: string;
email: string;
}
interface CreateUserBody {
name: string;
email: string;
}
interface UserParams {
id: string;
}
const fastify = Fastify({ logger: true });
fastify.post<{
Body: CreateUserBody;
}>('/users', async (request, reply) => {
const { name, email } = request.body;
const user: User = { id: 1, name, email };
reply.code(201);
return user;
});
fastify.get<{
Params: UserParams;
}>('/users/:id', async (request, reply) => {
const { id } = request.params;
return { id: parseInt(id) };
});
fastify.listen({ port: 3000 });
9. 실전 API 서버
const fastify = require('fastify')({ logger: true });
// 플러그인
fastify.register(require('@fastify/cors'));
// 데이터 저장소
const users = new Map();
let nextId = 1;
// 스키마
const createUserSchema = {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 2, maxLength: 50 },
email: { type: 'string', format: 'email' }
}
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
createdAt: { type: 'string' }
}
}
}
};
const getUsersSchema = {
querystring: {
type: 'object',
properties: {
page: { type: 'integer', minimum: 1, default: 1 },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }
}
},
response: {
200: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
}
};
// 라우트
fastify.get('/api/users', { schema: getUsersSchema }, async (request, reply) => {
const { page, limit } = request.query;
const allUsers = Array.from(users.values());
const start = (page - 1) * limit;
return allUsers.slice(start, start + limit);
});
fastify.get('/api/users/:id', async (request, reply) => {
const user = users.get(parseInt(request.params.id));
if (!user) {
reply.code(404);
return { error: 'User not found' };
}
return user;
});
fastify.post('/api/users', { schema: createUserSchema }, async (request, reply) => {
const user = {
id: nextId++,
...request.body,
createdAt: new Date().toISOString()
};
users.set(user.id, user);
reply.code(201);
return user;
});
fastify.put('/api/users/:id', async (request, reply) => {
const id = parseInt(request.params.id);
const user = users.get(id);
if (!user) {
reply.code(404);
return { error: 'User not found' };
}
const updated = { ...user, ...request.body, id };
users.set(id, updated);
return updated;
});
fastify.delete('/api/users/:id', async (request, reply) => {
const id = parseInt(request.params.id);
if (!users.delete(id)) {
reply.code(404);
return { error: 'User not found' };
}
reply.code(204);
return;
});
// 시작
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
결론
Fastify는 고성능 Node.js 웹 프레임워크로, Express보다 빠른 처리 속도를 제공합니다. JSON Schema 기반의 요청/응답 검증으로 안전하고 최적화된 API를 구축할 수 있습니다. 플러그인 아키텍처로 기능을 모듈화하고, 훅 시스템으로 요청 라이프사이클을 세밀하게 제어할 수 있습니다. 성능이 중요한 API 서버에 적합한 선택입니다.
728x90
반응형
'Node.js' 카테고리의 다른 글
| Node.js의 서버 사이드 렌더링(SSR) (0) | 2026.03.11 |
|---|---|
| Node.js의 NestJS 프레임워크 (0) | 2026.03.10 |
| Node.js의 Hapi.js 프레임워크 (0) | 2026.03.10 |
| Node.js의 Express.js 프레임워크 (0) | 2026.03.09 |
| Node.js의 HTTP 클라이언트 만들기 (0) | 2026.03.08 |