跳到主要内容

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

参考资源