Next.js、Prisma 和 MySQL 实践示例
1. 环境设置
a. 创建 Next.js 项目
首先,使用下面的命令创建一个新的 Next.js 应用:
npx create-next-app@latest my-next-prisma-app cd my-next-prisma-app
b. 安装依赖
安装 Prisma 和 MySQL 驱动程序:
npm install @prisma/client prisma mysql
c. 初始化 Prisma
初始化 Prisma,并创建一个 Prisma 配置文件:
npx prisma init
这将创建一个 prisma 文件夹,其中包含 schema.prisma 文件。
d. 环境变量
在 .env 文件中配置数据库连接字符串:
DATABASE_URL="mysql://user:password@localhost:3306/mydb?connection_limit=10"
2. 设置 Prisma 模型
在 prisma/schema.prisma 文件中定义数据模型。例如,一个简单的用户模型:
generator client { provider = "prisma-client-js"
} datasource db { provider = "mysql" url = env("DATABASE_URL")
} model User { id Int @id @default(autoincrement()) name String email String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
}
运行以下命令生成数据库迁移并更新数据库结构:
npx prisma migrate dev --name init npx prisma generate
查看数据库状态(可选):
可以使用以下命令查看数据库的当前状态:
npx prisma studio
3. 使用中间件处理连接
为了避免连接泄漏,使用 Prisma 中间件来处理连接。可以创建一个 db/prisma.ts 文件:
// data/prisma.ts
import { PrismaClient } from '@prisma/client';class PrismaInstance {private static instance: PrismaClient | undefined;private constructor() { }public static getInstance(): PrismaClient {if (!PrismaInstance.instance) {PrismaInstance.instance = new PrismaClient();}return PrismaInstance.instance;}
}// 导出 Prisma 实例获取方法
export const prisma = PrismaInstance.getInstance();
在上面的代码中,确保多个请求不会重复创建 Prisma 实例。
4. 创建 API 路由
在 pages/api 文件夹中创建 API 路由。例如,创建 users/route.ts 文件处理用户的 CRUD 操作:
// pages/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/db/prisma';export async function GET(request: NextRequest) {const { searchParams } = new URL(request.url);const id = searchParams.get('id');if (id) {// 获取特定用户const user = await prisma.user.findUnique({where: { id: Number(id) },});if (!user) {return NextResponse.json({ error: 'User not found' }, { status: 404 });}return NextResponse.json(user);} else {// 获取所有用户const users = await prisma.user.findMany();return NextResponse.json(users);}
}export async function POST(request: NextRequest) {const { name, email } = await request.json();const newUser = await prisma.user.create({data: { name, email },});return NextResponse.json(newUser, { status: 201 });
}export async function PUT(request: NextRequest) {const { searchParams } = new URL(request.url);const id = searchParams.get('id');if (!id) {return NextResponse.json({ error: 'User ID is required' }, { status: 400 });}const { name, email } = await request.json();const updatedUser = await prisma.user.update({where: { id: Number(id) },data: { name, email },});return NextResponse.json(updatedUser);
}export async function DELETE(request: NextRequest) {const { searchParams } = new URL(request.url);const id = searchParams.get('id');if (!id) {return NextResponse.json({ error: 'User ID is required' }, { status: 400 });}await prisma.user.delete({where: { id: Number(id) },});return new NextResponse(null, { status: 204 });
}
5. 创建前端页面
可以在 app/page.tsx 中创建一个简单的用户列表和表单来添加用户:
// app/page.tsx
'use client';import { useEffect, useState } from 'react';interface User {id: string;name: string;email: string;
}const Home = () => {const [users, setUsers] = useState<User[]>([]);const [name, setName] = useState('');const [email, setEmail] = useState('');const fetchUsers = async () => {try {const response = await fetch('/api/users');if (!response.ok) {throw new Error('Failed to fetch users');}const data = await response.json();setUsers(data);} catch (error) {console.error('Error fetching users:', error);}};const addUser = async (e: React.FormEvent<HTMLFormElement>) => {e.preventDefault();try {const response = await fetch('/api/users', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ name, email }),});if (!response.ok) {throw new Error('Failed to add user');}setName('');setEmail('');fetchUsers();} catch (error) {console.error('Error adding user:', error);}};useEffect(() => {fetchUsers();}, []);return (<div className="p-4 max-w-2xl mx-auto"><h1 className="text-3xl font-bold mb-6 text-center">User List</h1><form onSubmit={addUser} className="mb-8 flex flex-col sm:flex-row gap-4"><inputtype="text"value={name}onChange={(e) => setName(e.target.value)}placeholder="Name"requiredclassName="flex-grow p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"/><inputtype="email"value={email}onChange={(e) => setEmail(e.target.value)}placeholder="Email"requiredclassName="flex-grow p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"/><buttontype="submit"className="p-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-700">Add User</button></form>{users.length > 0 ? (<ul className="space-y-2">{users.map((user) => (<li key={user.id} className="p-3 bg-gray-100 rounded shadow"><span className="font-semibold">{user.name}</span> - {user.email}</li>))}</ul>) : (<p className="text-center text-gray-500">No users found. Add a user to get started!</p>)}</div>);
};export default Home;