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">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
required
className="flex-grow p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
className="flex-grow p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="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;