Express + Prisma REST API 实战
项目搭建
mkdir express-prisma
cd express-prisma
npm init -y
npm install express cors dotenv
npm install prisma @prisma/client
npm install typescript ts-node @types/node @types/express --save-dev
npx tsc --init
npx prisma init --datasource-provider postgresql
PrismaClient 单例
Express 是长驻 HTTP 服务器,一个实例生命周期内创建一次 PrismaClient 即可:
// src/prisma.ts
import { PrismaClient } from '@prisma/client'
export const prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
})
Express 不需要像 Next.js 那样用 globalThis 单例——因为 Express 不会热重载整个模块,一个 Node.js 进程里只创建一次 PrismaClient 就好。
完整示例:CRUD REST API
// src/index.ts
import express, { Request, Response } from 'express'
import cors from 'cors'
import { prisma } from './prisma'
const app = express()
app.use(cors())
app.use(express.json())
// GET /users
app.get('/users', async (req: Request, res: Response) => {
const users = await prisma.user.findMany({
include: { posts: true },
})
res.json(users)
})
// GET /users/:id
app.get('/users/:id', async (req: Request, res: Response) => {
const user = await prisma.user.findUnique({
where: { id: Number(req.params.id) },
include: { posts: true },
})
if (!user) return res.status(404).json({ error: '用户不存在' })
res.json(user)
})
// POST /users
app.post('/users', async (req: Request, res: Response) => {
try {
const user = await prisma.user.create({ data: req.body })
res.status(201).json(user)
} catch (err: any) {
if (err.code === 'P2002') {
return res.status(400).json({ error: '邮箱已存在' })
}
res.status(500).json({ error: '服务器错误' })
}
})
// PUT /users/:id
app.put('/users/:id', async (req: Request, res: Response) => {
try {
const user = await prisma.user.update({
where: { id: Number(req.params.id) },
data: req.body,
})
res.json(user)
} catch (err: any) {
if (err.code === 'P2025') {
return res.status(404).json({ error: '用户不存在' })
}
res.status(500).json({ error: '服务器错误' })
}
})
// DELETE /users/:id
app.delete('/users/:id', async (req: Request, res: Response) => {
try {
await prisma.user.delete({ where: { id: Number(req.params.id) } })
res.status(204).send()
} catch (err: any) {
if (err.code === 'P2025') {
return res.status(404).json({ error: '用户不存在' })
}
res.status(500).json({ error: '服务器错误' })
}
})
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
Prisma 错误码 → HTTP 状态码映射
| Prisma 错误码 | 含义 | HTTP 状态码 |
|---|---|---|
| P2002 | 唯一约束冲突(比如 email 重复) | 400 Bad Request |
| P2025 | 记录不存在 | 404 Not Found |
| P2003 | 外键约束失败(比如 Post 指向不存在的 User) | 400 Bad Request |
| P2014 | 因外键约束不能删 | 409 Conflict |
| P1001 / P1002 | 数据库连接失败 | 503 Service Unavailable |
中间件模式
Express 中间件里可以访问 prisma(只要把它作为模块导入):
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express'
import { prisma } from '../prisma'
export async function authenticate(req: Request, res: Response, next: NextFunction) {
const userId = req.headers['x-user-id']
if (!userId) return res.status(401).json({ error: '未认证' })
const user = await prisma.user.findUnique({ where: { id: Number(userId) } })
if (!user) return res.status(401).json({ error: '用户不存在' })
;(req as any).user = user
next()
}
Express 优雅关闭
Express 长驻进程不需要像一次性脚本那样频繁 $disconnect(),但在进程退出时(Ctrl+C、容器停止)应该断开:
process.on('SIGINT', async () => {
await prisma.$disconnect()
process.exit(0)
})
常见 Express + Prisma 模式
带分页的列表接口
app.get('/posts', async (req, res) => {
const page = Number(req.query.page) || 1
const limit = Number(req.query.limit) || 20
const [posts, total] = await Promise.all([
prisma.post.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.post.count(),
])
res.json({
posts,
pagination: { page, limit, total, pages: Math.ceil(total / limit) },
})
})
条件筛选查询
app.get('/users', async (req, res) => {
const { role, search } = req.query
const where: any = {}
if (role) where.role = role
if (search) {
where.OR = [
{ email: { contains: search } },
{ name: { contains: search } },
]
}
const users = await prisma.user.findMany({ where })
res.json(users)
})
嵌套写入创建关联
app.post('/users', async (req, res) => {
const user = await prisma.user.create({
data: {
email: req.body.email,
name: req.body.name,
posts: {
create: req.body.posts, // [{ title: '文章1' }, { title: '文章2' }]
},
},
})
res.status(201).json(user)
})