@maru

Fastify 5.10 가이드 — LogController와 TypeBox 실전 패턴
Node.js 생태계에서 고성능 백엔드 구축의 표준으로 자리 잡은 Fastify가 v5.10.x 업데이트를 통해 한층 더 견고하고 유연해졌습니다. 이번 메이저 버전은 오래된 레거시 구조를 걷어내고 완벽한 JSON 스키마 사양 준수를 의무화하는 한편, 새로운 로그 컨트롤러 계층을 통해 로깅 성능과 확장성을 대폭 강화했습니다. 복잡한 마이크로서비스 환경에서 런타임 성능을 극대화하고 견고한 타입 안전성을 확보하기 위해, 개발자가 반드시 적용해야 할 핵심 프로덕션 설계 패턴을 정리합니다.
1. LogController 도입 — 선언적으로 제어하는 요청 로그
Fastify v5.10.0의 가장 큰 아키텍처 변화는 새로운 로그 컨트롤러 계층의 도입입니다. 기존 최상위 옵션이던 disableRequestLogging과 requestIdLogLabel은 공식적으로 더 이상 권장되지 않으며(FSTDEP023, FSTDEP024), 향후 v6 버전에서 완전히 제거될 예정입니다. 대신 이제 개발자는 공식 LogController 클래스를 상속받아 선언적이고 객체지향적인 방식으로 로깅 수명 주기를 제어할 수 있습니다.
이 구조를 사용하면 /health 같은 헬스체크 경로의 로그를 생략하거나, 요청이 완료되는 시점에 특정 지표를 일관되게 기록하는 로직을 손쉽게 구현할 수 있습니다. Fastify 공식 깃허브의 PR #6580에 정의된 아키텍처에 따라 설계되어, 런타임 오버헤드를 유발하는 임시방편적인 훅 호출 없이 엔진 수준에서 고성능 로깅 필터링이 가능합니다.
다음은 LogController를 상속받아 /health 경로의 로그를 무시하고, 응답이 끝난 시점에 경과 시간을 기록하는 실전 예시입니다.
import Fastify, { LogController, FastifyRequest, FastifyReply } from 'fastify';
class CustomLogController extends LogController {
// /health 헬스체크 경로의 로그 기록을 제외합니다.
override isLogDisabled(request: FastifyRequest): boolean {
return request.url === '/health';
}
// 요청 완료 시점에 실행되어 최종 처리 지표를 기록합니다.
override requestCompleted(
error: Error | null,
request: FastifyRequest,
reply: FastifyReply,
metadata?: Record<string, unknown>
): void {
const duration = reply.elapsedTime; // Fastify v5에서 표준화된 경과 시간 속성
const level = error ? 'error' : 'info';
request.log[level]({
duration,
statusCode: reply.statusCode,
err: error
}, 'request completed');
}
}
const server = Fastify({
logController: new CustomLogController()
});위 코드처럼 기존의 reply.getResponseTime() 대신 v5에서 권장하는 reply.elapsedTime 속성을 활용해 처리 시간을 정밀하게 기록합니다. 이처럼 객체지향적 패턴으로 전환된 로그 컨트롤러 계층 덕분에 가독성이 뛰어나고 유지보수하기 쉬운 로깅 파이프라인을 구축할 수 있습니다.
2. TypeBox를 통한 스키마 기반 검증과 완벽한 타입 추론
Fastify v5에서는 기존의 불완전한 약식 단축 스키마 지원이 완전히 중단되었으며, 모든 라우트 검증에 반드시 표준 JSON 스키마 명세를 준수해야 합니다. 이러한 엄격한 검증 요건을 생산성 저하 없이 해결하는 가장 효과적인 도구가 바로 TypeBox입니다. TypeScript 개발자는 @fastify/type-provider-typebox를 결합하여 단 한 번의 정의로 런타임 검증과 컴파일 타임 타입 추론을 동시에 달성할 수 있습니다.
핵심은 Fastify 인스턴스를 생성할 때 withTypeProvider 메서드를 생성자 함수에 직접 메서드 체이닝으로 호출해야 한다는 점입니다. 이미 생성된 인스턴스 뒤에 단독으로 호출하면 변경된 타입 프로바이더 정보가 올바르게 전파되지 않으므로 주의가 필요합니다.
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type } from '@sinclair/typebox'
// 생성자 호출부에 직접 체이닝하여 인스턴스를 생성합니다.
const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()
fastify.post('/users', {
schema: {
body: Type.Object({
name: Type.String(),
email: Type.String({ format: 'email' })
}),
response: {
201: Type.Object({
id: Type.String(),
name: Type.String()
})
}
}
}, async (request, reply) => {
// request.body의 name과 email 타입이 자동으로 완벽하게 추론됩니다.
const { name, email } = request.body
return reply.status(201).send({
id: 'user-123',
name
})
})이 패턴을 적용하면 스키마를 정의하는 즉시 request.body 및 응답 데이터 구조가 TypeScript의 정적 타입 시스템과 완벽히 동기화됩니다. 추가적인 런타임 오버헤드가 없으며 성능이 검증된 Ajv 컴파일러를 그대로 사용하므로, 타입 안전성과 고성능을 동시에 확보해야 하는 엔터프라이즈 환경에서 필수적인 표준 패턴입니다.
3. avvio 기반 플러그인 아키텍처와 데코레이터 패턴
Fastify는 avvio 기반의 방향성 비순환 그래프(DAG) 플러그인 로더 시스템을 사용해 전역 상태 오염을 철저히 방지하는 엄격한 캡슐화를 기본으로 제공합니다. 이 구조 덕분에 한 곳에서 정의한 미들웨어나 훅이 의도치 않게 다른 라우트로 번지는 부작용을 원천 차단할 수 있습니다.
하지만 데이터베이스 클라이언트나 전역 캐시처럼 애플리케이션 전체에서 공유해야 하는 공통 리소스는 이 캡슐화를 깨고 상위 컨텍스트로 내보내야 합니다. 이때 공식 도우미 패키지인 fastify-plugin으로 플러그인을 감싸서 내보내면 캡슐화가 해제되어 데코레이터로 등록한 리소스를 전역에서 접근할 수 있게 됩니다. 반면, 일반적인 API 라우트 구성은 캡슐화를 그대로 유지하는 것이 모범 사례입니다.
다음은 TypeScript 환경에서 데이터베이스 클라이언트를 전역 데코레이터로 등록하고 안전하게 사용하기 위한 모범적인 구현 방식입니다.
import fp from 'fastify-plugin';
import { FastifyPluginAsync } from 'fastify';
// 가상의 데이터베이스 클라이언트
const dbClient = {
query: async (sql: string) => `Executed: ${sql}`,
};
// TypeScript 모듈 보강을 통한 인스턴스 타입 확장
declare module 'fastify' {
interface FastifyInstance {
db: typeof dbClient;
}
}
const dbPlugin: FastifyPluginAsync = async (fastify) => {
// fastify-plugin 덕분에 부모 컨텍스트로 노출됩니다.
fastify.decorate('db', dbClient);
};
export default fp(dbPlugin);이와 같이 fastify-plugin으로 플러그인을 래핑하고 모듈 보강을 추가하면 컴파일러 수준에서 타입 안정성을 완벽히 보장받으면서 유연하게 공유 자원을 관리할 수 있습니다.
결론: 프로덕션 마이그레이션 체크리스트
Fastify v5.10.x 기반으로 프로덕션 애플리케이션을 새로 구축하거나 업그레이드할 때 반드시 점검해야 할 핵심 체크리스트입니다.
첫째, 인프라의 런타임 환경을 확인해야 합니다. Fastify v5는 레거시 코드와 오래된 API 부채를 정리하며 최소 구동 요구사항을 Node.js 20 이상으로 격상했습니다. 이전 버전의 Node.js를 사용 중이라면 런타임 업데이트가 선행되어야 합니다.
둘째, 성능 측정과 라우트 확인 API의 변경 사항을 반영해야 합니다. 기존에 응답 지연 시간을 조회할 때 흔히 사용하던 reply.getResponseTime() 메서드가 제거되었으므로, 이제는 reply.elapsedTime 속성을 통해 값을 직접 읽어와야 합니다. 또한 특정 경로의 존재 여부를 검사하던 fastify.hasRoute() 메서드는 전방 일치 방식의 모호한 탐색을 지원하지 않으며, 정확히 일치하는 문자열 경로만 매칭하도록 동작이 엄격해졌습니다.
셋째, 스키마 검증과 로깅 파이프라인의 구조적 전환입니다. 과거에 부분적으로 허용되던 약식 단축 스키마는 지원이 완전히 중단되었으므로 모든 라우트 검증은 완전한 표준 JSON 스키마 명세를 준수해 정의해야 합니다. 로깅의 경우, 기존의 최상위 옵션인 disableRequestLogging이 비권장 대상으로 지정되었기 때문에 장기적으로는 LogController 클래스를 상속받아 수명 주기를 제어하는 방식으로 마이그레이션할 것을 권장합니다.
이러한 정돈 작업을 거쳐 구축된 Fastify는 불필요한 요청 오버헤드를 줄여 한층 강력해진 성능과 타입 안전성을 제공합니다. 고성능 마이크로서비스 인프라를 설계하거나 대규모 TypeScript 백엔드를 안정적으로 운영하고자 하는 조직에게 Fastify v5.10.x는 든든한 기술적 기반이 될 것입니다.
참고 링크