Prisma 中文文档 下载 App

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)
})