第 6 章:Node.js 后端开发¶
学习时间:5 小时 | 难度:⭐⭐⭐ 中级 | 前置知识:第 1-5 章,HTTP 基础
本章概览¶
本章使用 Fastify + TypeScript + Prisma 构建生产级 REST API,这是 2025 年 Node.js 后端最主流的技术栈。
学习目标:
- 掌握 Fastify 的类型安全路由和插件系统
- 使用 Zod 进行运行时数据验证与类型推断
- 掌握 Prisma ORM 的 TypeScript 集成
- 理解 JWT 认证与中间件设计
- 构建完整的 CRUD REST API
6.1 Fastify + TypeScript 项目搭建¶
Bash
mkdir api-server && cd api-server
pnpm init
pnpm add fastify @fastify/jwt @fastify/cors @fastify/helmet
pnpm add zod @fastify/type-provider-zod # 类型安全的请求验证
pnpm add @prisma/client # 数据库 ORM
pnpm add -D typescript tsx @types/node prisma
npx prisma init
TypeScript
// src/app.ts — Fastify 应用主文件
import Fastify from "fastify";
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "@fastify/type-provider-zod";
import cors from "@fastify/cors";
import helmet from "@fastify/helmet";
import jwt from "@fastify/jwt";
export function buildApp() {
const app = Fastify({
logger: {
level: process.env.NODE_ENV === "production" ? "info" : "debug",
// 生产环境用 pino-pretty 格式化日志
},
}).withTypeProvider<ZodTypeProvider>(); // 开启 Zod 类型提供器
// 注册编译器(Zod 作为验证器)
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
// 插件:安全头、CORS、JWT
app.register(helmet);
app.register(cors, {
origin: process.env.ALLOWED_ORIGINS?.split(",") ?? ["http://localhost:3000"],
credentials: true,
});
app.register(jwt, {
secret: process.env.JWT_SECRET!,
sign: { expiresIn: "7d" },
});
// 注册路由(模块化)
app.register(import("./routes/users"), { prefix: "/api/users" });
app.register(import("./routes/auth"), { prefix: "/api/auth" });
app.register(import("./routes/posts"), { prefix: "/api/posts" });
// 健康检查
app.get("/health", () => ({ status: "ok", timestamp: new Date().toISOString() }));
return app;
}
// src/server.ts — 启动入口
import { buildApp } from "./app";
const app = buildApp();
app.listen({ port: Number(process.env.PORT ?? 3000), host: "0.0.0.0" }, (err) => {
if (err) {
app.log.error(err);
process.exit(1);
}
});
6.2 Zod 运行时验证 + 类型推断¶
Zod 是 TypeScript 优先的数据校验库——一份 Schema,同时获得运行时验证和 TypeScript 类型:
TypeScript
// src/schemas/user.schema.ts
import { z } from "zod";
// 定义 Schema(运行时验证规则)
export const CreateUserSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email("Invalid email format"),
password: z.string().min(8).regex(/(?=.*[A-Z])(?=.*[0-9])/, "Must include uppercase and digit"),
role: z.enum(["admin", "user"]).default("user"),
});
export const UpdateUserSchema = CreateUserSchema.partial().omit({ password: true });
export const UserIdSchema = z.object({
id: z.string().uuid("Invalid user ID format"),
});
export const QueryUsersSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
search: z.string().optional(),
role: z.enum(["admin", "user"]).optional(),
});
// 从 Schema 提取 TypeScript 类型(无需重复定义!)
export type CreateUserDto = z.infer<typeof CreateUserSchema>;
export type UpdateUserDto = z.infer<typeof UpdateUserSchema>;
export type QueryUsersDto = z.infer<typeof QueryUsersSchema>;
6.3 类型安全的路由¶
TypeScript
// src/routes/users.ts
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod";
import { z } from "zod";
import { CreateUserSchema, UpdateUserSchema, UserIdSchema, QueryUsersSchema } from "../schemas/user.schema";
import { UserService } from "../services/user.service";
const userService = new UserService();
const usersRoutes: FastifyPluginAsyncZod = async (app) => {
// GET /api/users — 查询用户列表
app.get("/", {
schema: {
querystring: QueryUsersSchema,
response: {
200: z.object({
users: z.array(z.object({
id: z.string(),
name: z.string(),
email: z.string(),
role: z.enum(["admin", "user"]),
})),
total: z.number(),
page: z.number(),
}),
},
tags: ["users"],
summary: "List users with pagination",
},
}, async (request) => {
// request.query 类型完全推断自 QueryUsersSchema!
const { page, limit, search, role } = request.query;
return userService.findAll({ page, limit, search, role });
});
// GET /api/users/:id — 获取单个用户
app.get("/:id", {
schema: {
params: UserIdSchema,
},
}, async (request, reply) => {
const user = await userService.findById(request.params.id);
if (!user) {
return reply.status(404).send({ error: "User not found" });
}
return user;
});
// POST /api/users — 创建用户(需要 admin 权限)
app.post("/", {
onRequest: [app.authenticate, app.requireAdmin], // 认证中间件
schema: {
body: CreateUserSchema,
response: { 201: z.object({ id: z.string(), message: z.string() }) },
},
}, async (request, reply) => {
// request.body 类型完全推断:CreateUserDto
const newUser = await userService.create(request.body);
return reply.status(201).send({ id: newUser.id, message: "User created" });
});
// PATCH /api/users/:id — 更新用户
app.patch("/:id", {
onRequest: [app.authenticate],
schema: {
params: UserIdSchema,
body: UpdateUserSchema,
},
}, async (request, reply) => {
const updated = await userService.update(request.params.id, request.body);
if (!updated) return reply.status(404).send({ error: "User not found" });
return updated;
});
// DELETE /api/users/:id
app.delete("/:id", {
onRequest: [app.authenticate, app.requireAdmin],
schema: { params: UserIdSchema },
}, async (request, reply) => {
await userService.delete(request.params.id);
return reply.status(204).send();
});
};
export default usersRoutes;
6.4 Prisma ORM 集成¶
Text Only
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Post {
id String @id @default(uuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}
TypeScript
// src/lib/prisma.ts — 单例 Prisma 客户端
import { PrismaClient } from "@prisma/client";
// 防止开发环境热重载时创建多个连接
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error"] : ["error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
// src/services/user.service.ts
import { prisma } from "../lib/prisma";
import { type CreateUserDto, type UpdateUserDto, type QueryUsersDto } from "../schemas/user.schema";
import bcrypt from "bcryptjs";
export class UserService {
async findAll({ page, limit, search, role }: QueryUsersDto) {
const where = {
...(search ? {
OR: [
{ name: { contains: search, mode: "insensitive" as const } },
{ email: { contains: search, mode: "insensitive" as const } },
],
} : {}),
...(role ? { role: role.toUpperCase() as "ADMIN" | "USER" } : {}),
};
const [users, total] = await Promise.all([
prisma.user.findMany({
where,
skip: (page - 1) * limit,
take: limit,
select: { id: true, name: true, email: true, role: true, createdAt: true },
orderBy: { createdAt: "desc" },
}),
prisma.user.count({ where }),
]);
return { users, total, page };
}
async findById(id: string) {
return prisma.user.findUnique({
where: { id },
select: { id: true, name: true, email: true, role: true, createdAt: true },
});
}
async create(data: CreateUserDto) {
const hashedPassword = await bcrypt.hash(data.password, 12);
return prisma.user.create({
data: {
...data,
password: hashedPassword,
role: data.role.toUpperCase() as "ADMIN" | "USER",
},
});
}
async update(id: string, data: UpdateUserDto) {
return prisma.user.update({
where: { id },
data,
}).catch(() => null); // 如果不存在返回 null
}
async delete(id: string) {
return prisma.user.delete({ where: { id } });
}
}
6.5 JWT 认证中间件¶
TypeScript
// src/plugins/auth.ts
import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from "fastify";
import fp from "fastify-plugin";
// 扩展 Fastify 的类型声明
declare module "fastify" {
interface FastifyInstance {
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
requireAdmin: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
}
interface FastifyRequest {
user: { id: string; email: string; role: "ADMIN" | "USER" };
}
}
const authPlugin: FastifyPluginAsync = async (app) => {
// 验证 JWT
app.decorate("authenticate", async (request: FastifyRequest, reply: FastifyReply) => {
try {
await request.jwtVerify();
} catch {
reply.status(401).send({ error: "Unauthorized" });
}
});
// 要求管理员角色
app.decorate("requireAdmin", async (request: FastifyRequest, reply: FastifyReply) => {
if (request.user.role !== "ADMIN") {
reply.status(403).send({ error: "Forbidden: Admin role required" });
}
});
};
export default fp(authPlugin);
📌 本章小结¶
| 技术 | 版本 | 作用 |
|---|---|---|
| Fastify | 5.x | 高性能 HTTP 框架(比 Express 快 2-3 倍) |
| Zod | 3.x | 运行时验证 + 类型自动推断 |
| Prisma | 6.x | Type-safe ORM,自动生成类型 |
@fastify/type-provider-zod | — | Fastify 与 Zod 集成(Schema 自动类型推断) |
下一章:Next.js 15 全栈开发 + tRPC v11 端到端类型安全。
Node.js 22 · Fastify 5 · Prisma 6 · 2025