用 Fastify 构建 Web API:从零到完整 CRUD

手把手用 Fastify + Prisma + SQLite 构建博客 API:Schema 校验、路由声明、ORM 集成与错误处理全流程,体验 Fastify 的现代 API 开发方式。

最佳实践
应用接口与数据存储连接插画

Fastify API 开发的标准结构:JSON Schema 声明在路由上(校验+文档一体)→ handler 内用 Prisma 操作数据库 → 错误由 setErrorHandler 统一兜底。相比 Express,校验无需另装中间件;响应序列化的编译优化需配置 response schema。

项目搭建

mkdir blog-api && cd blog-api
npm init -y
npm install fastify@5 @prisma/client@6
npm install -D prisma@6
npx prisma init --datasource-provider sqlite

Prisma 模型

在初始化生成的 schema.prisma 中追加以下模型,保留原有 generator 和 datasource 配置。

// prisma/schema.prisma
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String
  published Boolean  @default(false)
  createdAt DateTime @default(now())
}
npx prisma migrate dev --name init   # 建表并生成客户端

Schema 驱动的路由

// 本文使用 Node.js 20+、Fastify 5 与 Prisma 6;Prisma 7 初始化方式不同
const fastify = require('fastify')({
  logger: true,
  ajv: { customOptions: { removeAdditional: false } },
});
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const idSchema = {
  type: 'object', required: ['id'],
  properties: { id: { type: 'integer', minimum: 1 } },
};

const postBodySchema = {
  type: 'object',
  required: ['title', 'content'],
  properties: {
    title: { type: 'string', minLength: 1, maxLength: 200 },
    content: { type: 'string', minLength: 1 },
    published: { type: 'boolean' },
  },
  additionalProperties: false,
};

fastify.post('/posts', { schema: { body: postBodySchema } }, async (request) => {
  const post = await prisma.post.create({ data: request.body });
  return post;   // 直接返回对象,Fastify 自动序列化
});

fastify.get('/posts', async () => {
  return prisma.post.findMany({ orderBy: { createdAt: 'desc' } });
});

fastify.get('/posts/:id', { schema: { params: idSchema } }, async (request, reply) => {
  const post = await prisma.post.findUnique({
    where: { id: Number(request.params.id) },
  });
  if (!post) return reply.code(404).send({ message: '文章不存在' });
  return post;
});

fastify.put('/posts/:id', { schema: { body: postBodySchema, params: idSchema } }, async (request, reply) => {
  try {
    return await prisma.post.update({
      where: { id: Number(request.params.id) },
      data: request.body,
    });
  } catch (error) {
    if (error.code === 'P2025') {
      return reply.code(404).send({ message: '文章不存在' });
    }
    throw error; // 连接失败等其他错误交给错误处理器,不能伪装成 404
  }
});

fastify.delete('/posts/:id', { schema: { params: idSchema } }, async (request, reply) => {
  try {
    await prisma.post.delete({ where: { id: Number(request.params.id) } });
    return reply.code(204).send();
  } catch (error) {
    if (error.code === 'P2025') {
      return reply.code(404).send({ message: '文章不存在' });
    }
    throw error; // 连接失败等其他错误交给错误处理器,不能伪装成 404
  }
});

fastify.listen({ port: 3000 });

Schema 校验失败的请求根本到不了 handler(自动 400);handler 内 return 即响应(只有声明匹配的 response schema 时才使用相应的编译序列化器,本例未声明响应 schema,不能据此断言性能或安全优势)。

Fastify 的独特优势在这个例子中尽显

  • 校验零依赖:不用装 express-validator,Schema 即规则;
  • 日志开箱:logger: true 内置 pino 结构化日志,每个请求自动带 ID;
  • async 原生:handler 就是 async 函数,异常自动进错误处理。

下一步

分页(cursor/offset)、认证(@fastify/jwt)、限流(@fastify/rate-limit)、OpenAPI 文档(@fastify/swagger 直接消费路由 Schema)——Fastify 官方插件全家桶一站配齐。

验证接口上线后的延迟与错误

用 Fastify 构建 Web API完成开发验证后,可选一条包含数据库或外部调用的业务路径接入观测云。按 Node.js APM 文档确认探针初始化顺序与框架兼容性,检查请求及下游 Span 是否连续,再按文档注入日志关联字段。以路由模板或稳定的操作名聚合,避免用用户 ID、动态 URL 或完整 GraphQL 查询作为高基数标签。

接入文档:Node.js APM 接入 · 日志采集 · 日志与链路关联。

常见问题(FAQ)

Q:request.body 在 Schema 之外还有字段会被注入吗?

本例显式设置 removeAdditional: false,结合 additionalProperties: false 才会拒绝多余字段;Fastify 默认配置通常会删除这些字段而不是报错。若没有设置 additionalProperties: false,多余字段可能保留;写库前用 pick 白名单字段更安全。

Q:Prisma 客户端在多文件间怎么共享?

写成 Fastify 插件(fastify-plugin 包装后 fastify.decorate('prisma', prisma)),路由里 fastify.prisma 访问,保证全应用单例。

Q:Fastify 和 Express 写法的主要心智差异?

三个:request/reply 替代 req/res;return data 替代 res.json(data)(reply.send 也可用);中间件概念变为插件+钩子。迁移详见本系列《从 Express 迁移到 Fastify》。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台