Fast and low overhead web framework for Node.js with powerful plugin architecture
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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
});