402 后端 API 开发
用 Codex 从数据库模型到 RESTful API 的全流程自动化生成
学习目标
完成本课程后,你将能够:
一、数据库模型设计
对话式模型设计
我要为任务管理平台设计数据库模型,实体包括:
- User(用户):邮箱、密码、姓名、角色
- Team(团队):名称、描述、创建者
- Project(项目):名称、描述、所属团队
- Task(任务):标题、描述、状态、优先级、指派人
- Comment(评论):内容、作者、所属任务
请生成 Prisma Schema,包含关联关系和索引Codex 生成的 Prisma Schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
ADMIN
MEMBER
VIEWER
}
enum TaskStatus {
TODO
IN_PROGRESS
REVIEW
DONE
}
enum Priority {
LOW
MEDIUM
HIGH
URGENT
}
model User {
id String @id @default(cuid())
email String @unique
password String
name String
avatar String?
role Role @default(MEMBER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teams TeamMember[]
tasks Task[] @relation("Assignee")
comments Comment[]
}
model Team {
id String @id @default(cuid())
name String
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members TeamMember[]
projects Project[]
}
model TeamMember {
id String @id @default(cuid())
role Role @default(MEMBER)
userId String
teamId String
user User @relation(fields: [userId], references: [id])
team Team @relation(fields: [teamId], references: [id])
@@unique([userId, teamId])
}
model Project {
id String @id @default(cuid())
name String
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamId String
team Team @relation(fields: [teamId], references: [id])
tasks Task[]
}
model Task {
id String @id @default(cuid())
title String
description String?
status TaskStatus @default(TODO)
priority Priority @default(MEDIUM)
dueDate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projectId String
project Project @relation(fields: [projectId], references: [id])
assigneeId String?
assignee User? @relation("Assignee", fields: [assigneeId], references: [id])
comments Comment[]
tags TaskTag[]
}
model Tag {
id String @id @default(cuid())
name String @unique
color String @default("#6366F1")
tasks TaskTag[]
}
model TaskTag {
taskId String
tagId String
task Task @relation(fields: [taskId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([taskId, tagId])
}
model Comment {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
taskId String
task Task @relation(fields: [taskId], references: [id])
authorId String
author User @relation(fields: [authorId], references: [id])
}执行迁移
/init 帮我生成 Prisma 迁移脚本并执行
npx prisma migrate dev --name init二、API 路由生成
自动生成 RESTful 路由
请为 Task 模型生成完整的 RESTful API,包括:
- GET /api/tasks — 列表(支持分页、筛选、排序)
- GET /api/tasks/:id — 详情
- POST /api/tasks — 创建
- PUT /api/tasks/:id — 更新
- DELETE /api/tasks/:id — 删除
- PATCH /api/tasks/:id/status — 状态变更
要求:
- Express + TypeScript
- 输入验证(Zod)
- 统一错误处理
- 权限检查中间件路由示例
// backend/src/routes/task.routes.ts
import { Router } from 'express';
import { TaskController } from '../controllers/task.controller';
import { authenticate } from '../middleware/auth';
import { authorize } from '../middleware/authorize';
import { validate } from '../middleware/validate';
import {
createTaskSchema,
updateTaskSchema,
taskQuerySchema,
} from '../schemas/task.schema';
const router = Router();
router.use(authenticate); // 所有任务路由需要认证
router
.route('/')
.get(validate(taskQuerySchema), TaskController.list)
.post(validate(createTaskSchema), TaskController.create);
router
.route('/:id')
.get(TaskController.getById)
.put(validate(updateTaskSchema), TaskController.update)
.delete(authorize('ADMIN'), TaskController.delete);
router.patch('/:id/status', TaskController.updateStatus);
export default router;三、输入验证与错误处理
Zod 验证 Schema
// backend/src/schemas/task.schema.ts
import { z } from 'zod';
export const createTaskSchema = z.object({
body: z.object({
title: z.string().min(1).max(200),
description: z.string().optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional(),
dueDate: z.string().datetime().optional(),
projectId: z.string().min(1),
assigneeId: z.string().optional(),
}),
});
export const updateTaskSchema = z.object({
body: z.object({
title: z.string().min(1).max(200).optional(),
description: z.string().optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional(),
status: z.enum(['TODO', 'IN_PROGRESS', 'REVIEW', 'DONE']).optional(),
dueDate: z.string().datetime().nullable().optional(),
assigneeId: z.string().nullable().optional(),
}),
});
export const taskQuerySchema = z.object({
query: z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
status: z.enum(['TODO', 'IN_PROGRESS', 'REVIEW', 'DONE']).optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional(),
search: z.string().optional(),
sortBy: z.enum(['createdAt', 'priority', 'dueDate']).default('createdAt'),
sortOrder: z.enum(['asc', 'desc']).default('desc'),
}),
});统一错误处理中间件
// backend/src/middleware/errorHandler.ts
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: err.message,
});
}
if (err instanceof ZodError) {
return res.status(400).json({
success: false,
error: '输入验证失败',
details: err.errors,
});
}
console.error('Unexpected error:', err);
return res.status(500).json({
success: false,
error: '服务器内部错误',
});
};四、控制器与服务层
控制器模式
Codex 生成标准的三层架构:
Controller(控制器)
→ 处理 HTTP 请求/响应
→ 调用 Service
Service(服务层)
→ 业务逻辑
→ 调用 Prisma Client
Prisma Client(数据层)
→ 数据库操作任务控制器示例
// backend/src/controllers/task.controller.ts
export class TaskController {
static async list(req: Request, res: Response, next: NextFunction) {
try {
const { page, limit, status, priority, search, sortBy, sortOrder } = req.query;
const result = await TaskService.findMany({
page, limit, status, priority, search, sortBy, sortOrder,
userId: req.user!.id,
});
res.json({ success: true, ...result });
} catch (error) {
next(error);
}
}
static async create(req: Request, res: Response, next: NextFunction) {
try {
const task = await TaskService.create({
...req.body,
creatorId: req.user!.id,
});
res.status(201).json({ success: true, data: task });
} catch (error) {
next(error);
}
}
}五、Swagger 文档自动生成
用 Codex 生成 API 文档注解
/docs 为所有 Task API 添加 Swagger JSDoc 注解,生成 OpenAPI 3.0 文档注解示例
/**
* @swagger
* /api/tasks:
* get:
* summary: 获取任务列表
* tags: [Tasks]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* - in: query
* name: status
* schema:
* type: string
* enum: [TODO, IN_PROGRESS, REVIEW, DONE]
* responses:
* 200:
* description: 任务列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* $ref: '#/components/schemas/Task'
*/配置 Swagger UI
// backend/src/swagger.ts
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
const options: swaggerJsdoc.Options = {
definition: {
openapi: '3.0.0',
info: {
title: 'TaskFlow API',
version: '1.0.0',
description: '团队任务管理平台 API 文档',
},
servers: [{ url: '/api' }],
},
apis: ['./src/routes/*.ts', './src/controllers/*.ts'],
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));访问 `http://localhost:3000/api-docs` 即可查看交互式 API 文档。
六、实战练习
练习 1:生成用户认证 API
请为 User 模型生成完整的认证 API:
- POST /api/auth/register
- POST /api/auth/login
- POST /api/auth/refresh-token
- GET /api/auth/me
- 使用 JWT 认证方案练习 2:编写集成测试
/test 为 Task API 编写集成测试用例,覆盖:
- 正常创建/更新/删除
- 参数验证失败
- 权限不足
- 不存在的资源练习 3:性能优化
/review 审查 TaskService 的查询,优化 N+1 问题七、本课总结
**核心要点:**
- Prisma Schema 由对话生成,验证关联关系后执行迁移
- RESTful API 采用 Controller-Service-Prisma 三层架构
- Zod 提供类型安全的输入验证和 TypeScript 类型推导
- 统一错误处理中间件捕获所有异常,避免未处理崩溃
- Swagger JSDoc 注解与代码共存,始终与实现保持同步
**下一步:** 模块 403 — 前端页面构建,搭建任务看板和交互式 UI