You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:57:41