如何使用Fastify、Ajv和Schema自定义错误格式?
问题
我希望使用Fastify、Ajv和Schema自定义错误格式,目标格式如下:
{ "message": "Bad request", "path": "/account/register", "status": 400, "timestamp": 1697094987, "errors": [ { "key": "email", "value": "This value is not a valid email address." } ] }
我编写了如下路由代码:
const routes = async (app: Application) => { app.post('/accounts/register', { schema }, async (req: FastifyRequest, reply: FastifyReply) => { reply.code(200); }); };
但当前得到的错误格式如下,请问如何调整为目标格式?
{ "statusCode": 400, "code": "FST_ERR_VALIDATION", "error": "Bad Request", "message": "body must have required property 'email'" }
解决方案
要实现目标错误格式,需要从Ajv错误格式化和Fastify全局错误处理两方面入手:
1. 配置Ajv自定义错误结构
在Fastify实例初始化时,自定义Ajv配置,开启全错误收集并优化错误信息输出:
import Fastify from 'fastify'; const app = Fastify({ ajv: { customOptions: { allErrors: true, // 收集所有验证错误,而非仅返回第一个 coerceTypes: true }, plugins: [ require('ajv-errors') // 用于在Schema中自定义错误提示 ] } });
同时可以在Schema中直接定义字段的错误提示文本,让输出更贴合需求:
const schema = { body: { type: 'object', required: ['email'], properties: { email: { type: 'string', format: 'email', errorMessage: { format: 'This value is not a valid email address.', required: '邮箱为必填字段' } } } } };
2. 配置Fastify全局错误钩子
通过setErrorHandler统一拦截验证错误,转换成目标格式:
app.setErrorHandler((error, request, reply) => { // 仅处理Fastify验证错误 if (error.code === 'FST_ERR_VALIDATION') { const formattedErrors = error.validation.map(err => { // 提取字段名,去掉body前缀 const key = err.instancePath.replace(/^\/body\//, '') || err.params.missingProperty; return { key, value: err.message }; }); reply.status(400).send({ message: 'Bad request', path: request.routeOptions.url, status: 400, timestamp: Math.floor(Date.now() / 1000), errors: formattedErrors }); } else { // 其他错误按默认逻辑处理 reply.status(error.statusCode || 500).send(error); } });
3. 完整整合示例
把配置和路由整合后的完整代码如下:
import Fastify, { Application, FastifyRequest, FastifyReply } from 'fastify'; const app = Fastify({ ajv: { customOptions: { allErrors: true, coerceTypes: true }, plugins: [require('ajv-errors')] } }); // 全局错误处理 app.setErrorHandler((error, request, reply) => { if (error.code === 'FST_ERR_VALIDATION') { const formattedErrors = error.validation.map(err => { const key = err.instancePath.replace(/^\/body\//, '') || err.params.missingProperty; return { key, value: err.message }; }); reply.status(400).send({ message: 'Bad request', path: request.routeOptions.url, status: 400, timestamp: Math.floor(Date.now() / 1000), errors: formattedErrors }); } else { reply.status(error.statusCode || 500).send({ message: error.message || 'Internal server error', status: error.statusCode || 500, timestamp: Math.floor(Date.now() / 1000) }); } }); // 定义Schema const schema = { body: { type: 'object', required: ['email'], properties: { email: { type: 'string', format: 'email', errorMessage: { format: 'This value is not a valid email address.', required: '邮箱为必填字段' } } } } }; // 注册路由 const routes = async (app: Application) => { app.post('/accounts/register', { schema }, async (req: FastifyRequest, reply: FastifyReply) => { reply.code(200).send({ message: '注册成功' }); }); }; // 启动服务 const start = async () => { try { await routes(app); await app.listen({ port: 3000 }); console.log('服务启动在 http://localhost:3000'); } catch (err) { app.log.error(err); process.exit(1); } }; start();
配置完成后,当请求触发验证错误时,就会输出你期望的目标格式。
内容的提问来源于stack exchange,提问作者Igor Bezlepkin
相关产品推荐
相关产品推荐

