Guides development of Fastify Node.js backend servers and REST APIs using TypeScript or JavaScript. Use when building, configuring, or debugging a Fastify application — including defining routes, implementing plugins, setting up JSON Schema validation, handling errors, optimising performance, managing authentication, configuring CORS and security headers, integrating databases, working with WebSockets, and deploying to production. Covers the full Fastify request lifecycle (hooks, serialization, logging with Pino) and TypeScript integration via strip types. Trigger terms: Fastify, Node.js server, REST API, API routes, backend framework, fastify.config, server.ts, app.ts.
95
95%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Fastify uses Pino for high-performance logging:
import Fastify from 'fastify';
const app = Fastify({
logger: true, // Enable default logging
});
// Or with configuration
const app = Fastify({
logger: {
level: 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
},
});Available log levels (in order of severity):
app.log.trace('Detailed debugging');
app.log.debug('Debugging information');
app.log.info('General information');
app.log.warn('Warning messages');
app.log.error('Error messages');
app.log.fatal('Fatal errors');Each request has its own logger with request context:
app.get('/users/:id', async (request) => {
// Logs include request ID automatically
request.log.info('Fetching user');
const user = await db.users.findById(request.params.id);
if (!user) {
request.log.warn({ userId: request.params.id }, 'User not found');
return { error: 'Not found' };
}
request.log.info({ userId: user.id }, 'User fetched');
return user;
});Always use structured logging with objects:
// GOOD - structured, searchable
request.log.info({
action: 'user_created',
userId: user.id,
email: user.email,
}, 'User created successfully');
request.log.error({
err: error,
userId: request.params.id,
operation: 'fetch_user',
}, 'Failed to fetch user');
// BAD - unstructured, hard to parse
request.log.info(`User ${user.id} created with email ${user.email}`);
request.log.error(`Failed to fetch user: ${error.message}`);function getLoggerConfig() {
if (process.env.NODE_ENV === 'production') {
return {
level: 'info',
// JSON output for log aggregation
};
}
if (process.env.NODE_ENV === 'test') {
return false; // Disable logging in tests
}
// Development
return {
level: 'debug',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
};
}
const app = Fastify({
logger: getLoggerConfig(),
});Customize how objects are serialized:
const app = Fastify({
logger: {
level: 'info',
serializers: {
// Customize request serialization
req: (request) => ({
method: request.method,
url: request.url,
headers: {
host: request.headers.host,
'user-agent': request.headers['user-agent'],
},
remoteAddress: request.ip,
}),
// Customize response serialization
res: (response) => ({
statusCode: response.statusCode,
}),
// Custom serializer for users
user: (user) => ({
id: user.id,
email: user.email,
// Exclude sensitive fields
}),
},
},
});
// Use custom serializer
request.log.info({ user: request.user }, 'User action');Prevent logging sensitive information:
import Fastify from 'fastify';
const app = Fastify({
logger: {
level: 'info',
redact: {
paths: [
'req.headers.authorization',
'req.headers.cookie',
'body.password',
'body.creditCard',
'*.password',
'*.secret',
'*.token',
],
censor: '[REDACTED]',
},
},
});Create child loggers with additional context:
app.addHook('onRequest', async (request) => {
// Add user context to all logs for this request
if (request.user) {
request.log = request.log.child({
userId: request.user.id,
userRole: request.user.role,
});
}
});
// Service-level child logger
const userService = {
log: app.log.child({ service: 'UserService' }),
async create(data) {
this.log.info({ email: data.email }, 'Creating user');
// ...
},
};Customize automatic request logging:
const app = Fastify({
logger: true,
disableRequestLogging: true, // Disable default request/response logs
});
// Custom request logging
app.addHook('onRequest', async (request) => {
request.log.info({
method: request.method,
url: request.url,
query: request.query,
}, 'Request received');
});
app.addHook('onResponse', async (request, reply) => {
request.log.info({
statusCode: reply.statusCode,
responseTime: reply.elapsedTime,
}, 'Request completed');
});Properly log errors with stack traces:
app.setErrorHandler((error, request, reply) => {
// Log error with full details
request.log.error({
err: error, // Pino serializes error objects properly
url: request.url,
method: request.method,
body: request.body,
query: request.query,
}, 'Request error');
reply.code(error.statusCode || 500).send({
error: error.message,
});
});
// In handlers
app.get('/data', async (request) => {
try {
return await fetchData();
} catch (error) {
request.log.error({ err: error }, 'Failed to fetch data');
throw error;
}
});Configure where logs are sent:
import { createWriteStream } from 'node:fs';
// File output
const app = Fastify({
logger: {
level: 'info',
stream: createWriteStream('./app.log'),
},
});
// Multiple destinations with pino.multistream
import pino from 'pino';
const streams = [
{ stream: process.stdout },
{ stream: createWriteStream('./app.log') },
{ level: 'error', stream: createWriteStream('./error.log') },
];
const app = Fastify({
logger: pino({ level: 'info' }, pino.multistream(streams)),
});Use pino-roll for log rotation:
node app.js | pino-roll --frequency daily --extension .logOr configure programmatically:
import { createStream } from 'rotating-file-stream';
const stream = createStream('app.log', {
size: '10M', // Rotate every 10MB
interval: '1d', // Rotate daily
compress: 'gzip',
path: './logs',
});
const app = Fastify({
logger: {
level: 'info',
stream,
},
});Format logs for aggregation services:
// For ELK Stack, Datadog, etc. - use default JSON format
const app = Fastify({
logger: {
level: 'info',
// Default JSON output works with most log aggregators
},
});
// Add service metadata
const app = Fastify({
logger: {
level: 'info',
base: {
service: 'user-api',
version: process.env.APP_VERSION,
environment: process.env.NODE_ENV,
},
},
});Use request IDs for distributed tracing:
const app = Fastify({
logger: true,
requestIdHeader: 'x-request-id', // Use incoming header
genReqId: (request) => {
// Generate ID if not provided
return request.headers['x-request-id'] || crypto.randomUUID();
},
});
// Forward request ID to downstream services
app.addHook('onRequest', async (request) => {
request.requestId = request.id;
});
// Include in outgoing requests
const response = await fetch('http://other-service/api', {
headers: {
'x-request-id': request.id,
},
});Pino is fast, but consider:
// Avoid string concatenation in log calls
// BAD
request.log.info('User ' + user.id + ' did ' + action);
// GOOD
request.log.info({ userId: user.id, action }, 'User action');
// Use appropriate log levels
// Don't log at info level in hot paths
if (app.log.isLevelEnabled('debug')) {
request.log.debug({ details: expensiveToCompute() }, 'Debug info');
}