Extend Fastify instances, requests, and replies with custom properties and methods.
Add properties and methods to the Fastify instance.
/**
* Add property or method to Fastify instance
* @param name - Property name
* @param value - Property value or method function
* @param dependencies - Array of required decorators
* @returns FastifyInstance for method chaining
*/
decorate(name: string, value: any, dependencies?: string[]): FastifyInstance;
/**
* Check if decorator exists on instance
* @param name - Decorator name to check
* @returns Boolean indicating if decorator exists
*/
hasDecorator(name: string): boolean;
/**
* Get decorator value from instance
* @param name - Decorator name
* @returns Decorator value
*/
getDecorator(name: string): any;Usage Examples:
// Add utility method
fastify.decorate('utils', {
formatDate: (date) => date.toISOString(),
generateId: () => Math.random().toString(36).substr(2, 9),
validateEmail: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
});
// Use decorated utility
fastify.get('/current-time', async (request, reply) => {
return {
time: fastify.utils.formatDate(new Date()),
id: fastify.utils.generateId()
};
});
// Add database connection
fastify.decorate('db', database);
// Use in routes
fastify.get('/users', async (request, reply) => {
const users = await fastify.db.query('SELECT * FROM users');
return users;
});Add properties and methods to request objects.
/**
* Add property or method to request objects
* @param name - Property name
* @param value - Property value or method function
* @param dependencies - Array of required decorators
* @returns FastifyInstance for method chaining
*/
decorateRequest(name: string, value: any, dependencies?: string[]): FastifyInstance;
/**
* Check if request decorator exists
* @param name - Decorator name to check
* @returns Boolean indicating if decorator exists
*/
hasRequestDecorator(name: string): boolean;Usage Examples:
// Add user context to requests
fastify.decorateRequest('user', null);
fastify.decorateRequest('getUserRole', function() {
return this.user?.role || 'guest';
});
// Use in hooks
fastify.addHook('preHandler', async (request, reply) => {
const token = request.headers.authorization?.replace('Bearer ', '');
if (token) {
request.user = await verifyToken(token);
}
});
// Use in route handlers
fastify.get('/profile', async (request, reply) => {
if (!request.user) {
reply.code(401).send({ error: 'Unauthorized' });
return;
}
return {
user: request.user,
role: request.getUserRole()
};
});
// Add request tracking
fastify.decorateRequest('startTime', null);
fastify.decorateRequest('getElapsed', function() {
return Date.now() - this.startTime;
});
fastify.addHook('onRequest', (request, reply, done) => {
request.startTime = Date.now();
done();
});Add properties and methods to reply objects.
/**
* Add property or method to reply objects
* @param name - Property name
* @param value - Property value or method function
* @param dependencies - Array of required decorators
* @returns FastifyInstance for method chaining
*/
decorateReply(name: string, value: any, dependencies?: string[]): FastifyInstance;
/**
* Check if reply decorator exists
* @param name - Decorator name to check
* @returns Boolean indicating if decorator exists
*/
hasReplyDecorator(name: string): boolean;Usage Examples:
// Add success response helper
fastify.decorateReply('success', function(data, message = 'Success') {
return this.send({
success: true,
message,
data,
timestamp: new Date().toISOString()
});
});
// Add error response helper
fastify.decorateReply('error', function(message, code = 400) {
return this.code(code).send({
success: false,
error: message,
timestamp: new Date().toISOString()
});
});
// Use in route handlers
fastify.get('/users/:id', async (request, reply) => {
try {
const user = await findUser(request.params.id);
if (!user) {
return reply.error('User not found', 404);
}
return reply.success(user, 'User retrieved successfully');
} catch (err) {
return reply.error('Failed to retrieve user', 500);
}
});
// Add pagination helper
fastify.decorateReply('paginated', function(data, page, limit, total) {
return this.send({
data,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
});Manage decorator dependencies and load order.
// Decorator with dependencies
fastify.decorate('config', appConfig);
fastify.decorate('database', databaseConnection, ['config']);
fastify.decorate('cache', cacheInstance, ['config', 'database']);
// Fastify ensures decorators are available in the right order
fastify.get('/status', async (request, reply) => {
return {
database: await fastify.database.status(),
cache: await fastify.cache.status(),
config: fastify.config.environment
};
});
// Plugin with dependencies
async function databasePlugin(fastify, options) {
// This plugin requires 'config' decorator
if (!fastify.hasDecorator('config')) {
throw new Error('Config decorator required');
}
fastify.decorate('db', createDatabase(fastify.config.database));
}
// Register with dependencies
await fastify.register(configPlugin);
await fastify.register(databasePlugin); // Will have config availableCreate decorators with getter and setter functions.
// Decorator with getter/setter
fastify.decorate('currentUser', {
getter() {
return this.user || null;
},
setter(user) {
this.user = user;
this.log.info({ userId: user.id }, 'User context set');
}
});
// Usage in middleware
fastify.addHook('preHandler', async (request, reply) => {
const token = request.headers.authorization;
if (token) {
const user = await validateToken(token);
fastify.currentUser = user; // Uses setter
}
});
// Usage in routes
fastify.get('/me', async (request, reply) => {
const user = fastify.currentUser; // Uses getter
if (!user) {
reply.code(401).send({ error: 'Not authenticated' });
return;
}
return user;
});Decorators are scoped to plugin contexts for encapsulation.
// Parent context
fastify.decorate('parentHelper', () => 'from parent');
await fastify.register(async function childPlugin(fastify) {
// Child has access to parent decorators
console.log(fastify.parentHelper()); // "from parent"
// Child-specific decorator
fastify.decorate('childHelper', () => 'from child');
fastify.get('/child-route', (request, reply) => {
return {
parent: fastify.parentHelper(),
child: fastify.childHelper()
};
});
});
// Parent cannot access child decorators
// console.log(fastify.childHelper()); // Error!Complex decoration scenarios and best practices.
// Service container pattern
fastify.decorate('services', {});
// Register services
fastify.services.user = {
async findById(id) {
return await userRepository.findById(id);
},
async create(data) {
return await userRepository.create(data);
}
};
fastify.services.email = {
async send(to, subject, body) {
return await emailProvider.send(to, subject, body);
}
};
// Factory pattern for decorators
function createApiDecorator(baseUrl, headers = {}) {
return {
async get(path) {
const response = await fetch(`${baseUrl}${path}`, { headers });
return response.json();
},
async post(path, data) {
const response = await fetch(`${baseUrl}${path}`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
}
};
}
// Use factory
fastify.decorate('api', createApiDecorator('https://api.example.com', {
'Authorization': 'Bearer token'
}));
// Conditional decorations
if (process.env.NODE_ENV === 'development') {
fastify.decorate('debug', {
logRequest: (request) => console.log(request.method, request.url),
logResponse: (reply) => console.log('Response:', reply.statusCode)
});
} else {
fastify.decorate('debug', {
logRequest: () => {},
logResponse: () => {}
});
}Extend types for decorated properties.
// Extend Fastify types
declare module 'fastify' {
interface FastifyInstance {
config: AppConfig;
db: Database;
services: ServiceContainer;
}
interface FastifyRequest {
user?: User;
startTime: number;
getUserRole(): string;
}
interface FastifyReply {
success(data: any, message?: string): FastifyReply;
error(message: string, code?: number): FastifyReply;
}
}
// Use with full type safety
fastify.get('/users', async (request, reply) => {
const users = await fastify.db.users.findAll(); // Typed
return reply.success(users); // Typed
});