Fastify
Fastify 是一个高性能的 Node.js Web 框架,专注于提供最好的开发体验和极致的性能。它的主要特点是快速的路由查找算法和插件架构,使其成为构建高性能 Web 应用的理想选择。
1. 基础使用
1.1 安装和设置
# 创建新项目
mkdir fastify-app
cd fastify-app
npm init -y
# 安装 Fastify
npm install fastify
# 安装常用插件
npm install @fastify/cors @fastify/swagger @fastify/jwt @fastify/static
1.2 基本应用
// app.js
const fastify = require('fastify')({
logger: true
});
// 声明路由
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
// 启动服务器
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
2. 路由系统
2.1 基础路由
// routes/users.js
async function routes(fastify, options) {
// GET /users
fastify.get('/users', async (request, reply) => {
return [{ id: 1, name: 'John' }];
});
// GET /users/:id
fastify.get('/users/:id', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' }
}
}
}
}, async (request, reply) => {
const { id } = request.params;
return { id, name: 'John' };
});
// POST /users
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
}
}
}
}, async (request, reply) => {
const user = request.body;
reply.code(201);
return user;
});
}
module.exports = routes;
// app.js
fastify.register(require('./routes/users'));
2.2 路由选项和钩子
fastify.route({
method: 'GET',
url: '/custom',
schema: {
querystring: {
name: { type: 'string' },
age: { type: 'integer' }
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
preHandler: async (request, reply) => {
// 前置处理器
},
handler: async (request, reply) => {
return { hello: 'world' };
}
});
// 路由钩子
fastify.addHook('onRequest', async (request, reply) => {
// 请求开始时执行
});
fastify.addHook('preHandler', async (request, reply) => {
// 处理请求前执行
});
fastify.addHook('onResponse', async (request, reply) => {
// 响应发送后执行
});
3. 插件系统
3.1 创建插件
// plugins/db.js
const fp = require('fastify-plugin');
const mongoose = require('mongoose');
async function dbConnector(fastify, options) {
const url = options.url || 'mongodb://localhost/fastify-app';
mongoose.connect(url);
fastify.decorate('mongo', mongoose);
}
module.exports = fp(dbConnector);
// 使用插件
fastify.register(require('./plugins/db'), {
url: 'mongodb://localhost/fastify-app'
});
3.2 常用插件
const fastify = require('fastify')();
// CORS
await fastify.register(require('@fastify/cors'), {
origin: true
});
// 静态文件
await fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'public')
});
// Swagger
await fastify.register(require('@fastify/swagger'), {
routePrefix: '/documentation',
swagger: {
info: {
title: 'Fastify API',
description: 'API documentation',
version: '1.0.0'
}
},
exposeRoute: true
});
// JWT
await fastify.register(require('@fastify/jwt'), {
secret: 'your-secret-key'
});
4. 验证和序列化
4.1 请求验证
const schema = {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
},
additionalProperties: false
},
querystring: {
type: 'object',
properties: {
limit: { type: 'integer', default: 10 },
offset: { type: 'integer', default: 0 }
}
},
params: {
type: 'object',
properties: {
id: { type: 'string', pattern: '^[0-9a-fA-F]{24}$' }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
};
fastify.post('/users/:id', { schema }, async function (request, reply) {
// 请求已经被验证
const { id } = request.params;
const user = request.body;
return { id, ...user };
});
4.2 序列化
const schema = {
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
};
fastify.get('/users/:id', {
schema,
serializerCompiler: ({ schema }) => {
return data => JSON.stringify(data);
}
}, async function (request, reply) {
return { id: '1', name: 'John', email: 'john@example.com' };
});
5. 错误处理
5.1 错误处理器
// 自定义错误
class CustomError extends Error {
constructor(message, statusCode = 400) {
super(message);
this.statusCode = statusCode;
}
}
// 全局错误处理器
fastify.setErrorHandler(function (error, request, reply) {
if (error instanceof CustomError) {
reply.status(error.statusCode).send({
error: error.message
});
return;
}
request.log.error(error);
reply.status(500).send({ error: 'Internal Server Error' });
});
// 处理 404 错误
fastify.setNotFoundHandler(function (request, reply) {
reply.status(404).send({ error: 'Route not found' });
});
5.2 错误处理最佳实践
// 异步路由处理
fastify.get('/async', async (request, reply) => {
try {
const result = await someAsyncOperation();
return result;
} catch (err) {
throw new CustomError(err.message);
}
});
// 使用 schema 验证
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['email'],
properties: {
email: { type: 'string', format: 'email' }
}
}
},
errorHandler: function (error, request, reply) {
if (error.validation) {
reply.status(400).send({
error: 'Validation Error',
messages: error.validation
});
return;
}
reply.send(error);
}
}, async function (request, reply) {
// 处理请求
});
6. 性能优化
6.1 路由优化
// 使用 fastify-routes-stats 插件监控路由性能
const routeStats = require('fastify-routes-stats');
fastify.register(routeStats);
// 定期检查路由性能
setInterval(() => {
const stats = fastify.routeStats.getStats();
console.log(stats);
}, 60000);
// 使用 onSend 钩子记录响应时间
fastify.addHook('onSend', async (request, reply, payload) => {
const responseTime = reply.getResponseTime();
if (responseTime > 100) {
request.log.warn({
url: request.url,
method: request.method,
responseTime
}, 'Slow response detected');
}
return payload;
});
6.2 缓存策略
const fastifyCache = require('@fastify/caching');
// 注册缓存插件
fastify.register(fastifyCache, {
privacy: fastifyCache.privacy.PUBLIC,
expiresIn: 300 // 5分钟
});
// 使用缓存的路由
fastify.get('/cached', {
cache: {
expiresIn: 300
}
}, async (request, reply) => {
const data = await expensiveOperation();
return data;
});
// 条件缓存
fastify.get('/conditional-cache', async (request, reply) => {
const etag = calculateEtag(request);
reply.header('ETag', etag);
if (request.headers['if-none-match'] === etag) {
reply.code(304);
return;
}
const data = await getData();
return data;
});
7. 测试
7.1 单元测试
// test/routes/users.test.js
const { test } = require('tap');
const build = require('../helper');
test('GET /users', async t => {
const app = build(t);
const response = await app.inject({
method: 'GET',
url: '/users'
});
t.equal(response.statusCode, 200);
t.same(JSON.parse(response.payload), [
{ id: 1, name: 'John' }
]);
});
test('POST /users', async t => {
const app = build(t);
const response = await app.inject({
method: 'POST',
url: '/users',
payload: {
name: 'John',
email: 'john@example.com'
}
});
t.equal(response.statusCode, 201);
const payload = JSON.parse(response.payload);
t.equal(payload.name, 'John');
});
7.2 集成测试
// test/integration/auth.test.js
const { test } = require('tap');
const build = require('../helper');
test('authentication', async t => {
const app = build(t);
// 登录测试
const loginResponse = await app.inject({
method: 'POST',
url: '/login',
payload: {
username: 'test',
password: 'password'
}
});
t.equal(loginResponse.statusCode, 200);
const { token } = JSON.parse(loginResponse.payload);
// 验证受保护的路由
const protectedResponse = await app.inject({
method: 'GET',
url: '/protected',
headers: {
authorization: `Bearer ${token}`
}
});
t.equal(protectedResponse.statusCode, 200);
});
8. 部署
8.1 生产环境配置
// config/production.js
const path = require('path');
module.exports = {
fastify: {
logger: {
level: 'info',
file: path.join(__dirname, '../logs/server.log')
},
trustProxy: true
},
server: {
port: process.env.PORT || 3000,
address: '0.0.0.0'
},
cors: {
origin: process.env.CORS_ORIGIN,
methods: ['GET', 'POST', 'PUT', 'DELETE']
}
};
// app.js
const config = require('./config/production');
const fastify = require('fastify')(config.fastify);
// 生产环境安全设置
if (process.env.NODE_ENV === 'production') {
fastify.register(require('@fastify/helmet'));
fastify.register(require('@fastify/rate-limit'), {
max: 100,
timeWindow: '1 minute'
});
}
8.2 PM2 部署
// ecosystem.config.js
module.exports = {
apps: [{
name: 'fastify-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
PORT: 80
},
node_args: '--max_old_space_size=4096'
}]
};
// 使用 PM2 启动
// pm2 start ecosystem.config.js --env production