@maru

Fastify 5.10 Guide — LogController and TypeBox Production Patterns
Fastify, established as the standard for building high-performance backends in the Node.js ecosystem, has become even more robust and flexible with the v5.10.x update. This major version removes outdated legacy structures and enforces strict adherence to JSON schema specifications, while significantly enhancing logging performance and extensibility through a new LogController layer. Below, I outline key production design patterns that developers must apply to maximize runtime performance and ensure solid type safety in complex microservice environments.
1. Introducing LogController — Declarative Request Logging
The most significant architectural change in Fastify v5.10.0 is the introduction of the new LogController layer. Previous top-level options like disableRequestLogging and requestIdLogLabel are officially deprecated (FSTDEP023, FSTDEP024) and are slated for complete removal in v6. Instead, developers can now inherit from the official LogController class to control the logging lifecycle in a declarative and object-oriented manner.
Using this structure, you can easily implement logic to omit logs for health check paths like /health or consistently log specific metrics when a request completes. Designed according to the architecture defined in Fastify's official GitHub PR #6580, it enables high-performance logging filtering at the engine level without the need for ad-hoc hook calls that cause runtime overhead.
Below is a practical example of inheriting LogController to ignore logs for the /health path and record the elapsed time upon completion of the response.
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()
});As seen in the code above, instead of the existing reply.getResponseTime(), we utilize the reply.elapsedTime property recommended in v5 to precisely record processing time. Thanks to the shift to an object-oriented LogController layer, you can build a logging pipeline that is highly readable and easy to maintain.
2. Schema-based Validation and Perfect Type Inference with TypeBox
In Fastify v5, support for legacy, incomplete shorthand schemas has been completely discontinued, and all route validation must adhere to the standard JSON schema specification. TypeBox is the most effective tool to resolve these strict validation requirements without sacrificing productivity. TypeScript developers can combine it with @fastify/type-provider-typebox to achieve runtime validation and compile-time type inference simultaneously with a single definition.
The key is to call the withTypeProvider method directly via method chaining on the constructor function when creating the Fastify instance. Care must be taken; if called alone after the instance is already created, the updated type provider information will not propagate correctly.
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
})
})By applying this pattern, request.body and response data structures are perfectly synchronized with TypeScript's static type system as soon as the schema is defined. There is no additional runtime overhead, and because it utilizes the performance-proven Ajv compiler, this is an essential standard pattern for enterprise environments that require both type safety and high performance.
3. avvio-based Plugin Architecture and Decorator Pattern
Fastify provides strict encapsulation by default, utilizing an avvio-based Directed Acyclic Graph (DAG) plugin loader system to thoroughly prevent global state contamination. This structure effectively prevents side effects where middleware or hooks defined in one area unintentionally bleed into other routes.
However, common resources that must be shared across the entire application—such as database clients or global caches—need to break this encapsulation and be exported to a higher context. When you wrap a plugin with the official fastify-plugin helper package, encapsulation is lifted, allowing resources registered as decorators to be accessed globally. Conversely, for general API route configuration, maintaining encapsulation remains the best practice.
Below is an exemplary implementation for registering and safely using a database client as a global decorator in a TypeScript environment.
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);By wrapping the plugin with fastify-plugin and adding module augmentation in this way, you can manage shared resources flexibly while ensuring complete type safety at the compiler level.
Conclusion: Production Migration Checklist
Here is a key checklist to review when building or upgrading production applications based on Fastify v5.10.x.
First, check your infrastructure's runtime environment. Fastify v5 has cleared away legacy code and technical debt, raising the minimum requirement to Node.js 20 or higher. If you are using an older version of Node.js, a runtime update must come first.
Second, reflect the changes in performance measurement and route inspection APIs. The reply.getResponseTime() method, commonly used to query response latency, has been removed; you must now read the value directly via the reply.elapsedTime property. Additionally, the fastify.hasRoute() method, which checked for the existence of specific paths, no longer supports ambiguous forward-match lookups and has been tightened to match only exact string path matches.
Third, consider the structural shift in schema validation and the logging pipeline. As shorthand schemas, which were partially permitted in the past, are no longer supported, all route validation must be defined in strict adherence to the full standard JSON schema specification. For logging, because the top-level disableRequestLogging option is now deprecated, we recommend long-term migration to a lifecycle control approach by inheriting the LogController class.
Fastify built through this housekeeping provides enhanced performance and type safety by reducing unnecessary request overhead. For organizations designing high-performance microservice infrastructure or reliably operating large-scale TypeScript backends, Fastify v5.10.x will serve as a solid technical foundation.
Reference Links