0️⃣ 开场彩蛋:一张图先声夺人
没图?那就画一张 ASCII 海报!
vbnet
┌─────────────────────────────┐
│ Next.js Full Stack │
│ CRUD Musical │
│ │
│ 🎤 Next 💃 Prisma 🥁 Pg │
└─────────────────────────────┘
1️⃣ 舞台搭建:项目初始化
角色 | 作用 | 安装命令 |
---|---|---|
Next.js 13 App Router | 前端 + API | npx create-next-app@latest next-prisma-crud --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" |
Prisma ORM | 数据舞者 | npm i prisma @prisma/client |
PostgreSQL | 幕后仓库 | Docker 一行起: docker run --name pg -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:15 |
小贴士:
把
postgres
密码设成postgres
,就像把家门钥匙放在门垫下------方便但别上生产 🏠
2️⃣ 数据库建模:把剧本写成 schema
文件 prisma/schema.prisma
:
prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
title String
content String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
小图标:📜 "Prisma Schema:把 DDL 写成十四行诗"
3️⃣ 环境变量:别让密码裸奔
.env
:
ini
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres?schema=public"
把
.env
加入.gitignore
,否则 GitHub 机器人会私信你:"兄弟,密码走光了。"
4️⃣ 一键迁移:让数据库学会新舞步
bash
npx prisma migrate dev --name init
npx prisma generate
这两行命令做了什么?
migrate
:把 schema 翻译成 SQL,像同声传译。generate
:给 Prisma Client 生成 TypeScript 类型,像给演员发剧本。
5️⃣ 演员上场:Prisma Client 的单例模式
src/lib/prisma.ts
:
ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
小图标:🎭 "单例模式:确保全场只有一位女主角"
6️⃣ 全栈 CRUD:一场四幕歌剧
🎼 第 1 幕:CREATE --- 新生
src/app/api/posts/route.ts
ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function POST(req: NextRequest) {
const { title, content } = await req.json();
const post = await prisma.post.create({ data: { title, content } });
return NextResponse.json(post, { status: 201 });
}
前端 src/app/new/page.tsx
tsx
'use client';
import { useRouter } from 'next/navigation';
export default function NewPost() {
const router = useRouter();
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = new FormData(e.currentTarget);
await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify({
title: form.get('title'),
content: form.get('content'),
}),
});
router.push('/');
}
return (
<form onSubmit={handleSubmit} className="space-y-4 p-8">
<input name="title" placeholder="标题" className="border rounded w-full" />
<textarea name="content" placeholder="内容" className="border rounded w-full h-32" />
<button className="bg-blue-600 text-white px-4 py-2 rounded">发布</button>
</form>
);
}
观众反馈:🎉 "创建成功,数据库鼓掌 1 次"
🎼 第 2 幕:READ --- 回眸
src/app/api/posts/route.ts
(GET)
ts
export async function GET() {
const posts = await prisma.post.findMany({ orderBy: { createdAt: 'desc' } });
return NextResponse.json(posts);
}
src/app/page.tsx
tsx
export default async function Home() {
const posts: Post[] = await (await fetch('http://localhost:3000/api/posts', { cache: 'no-store' })).json();
return (
<ul>
{posts.map(p => (
<li key={p.id} className="border-b p-4">
<h2 className="text-xl font-bold">{p.title}</h2>
<p>{p.content}</p>
</li>
))}
</ul>
);
}
小图标:👀 "服务端组件:SSR 免费,不用 Vercel 也请客"
🎼 第 3 幕:UPDATE --- 易容
src/app/api/posts/[id]/route.ts
ts
export async function PUT(req: NextRequest, { params }: { params: { id: string } }) {
const data = await req.json();
const post = await prisma.post.update({
where: { id: Number(params.id) },
data,
});
return NextResponse.json(post);
}
前端加一个编辑按钮,路由 src/app/edit/[id]/page.tsx
,复用 NewPost
组件并预填表单即可。
观众反馈:✍️ "改完标题,数据库说:我年轻了 5 岁"
🎼 第 4 幕:DELETE --- 谢幕
src/app/api/posts/[id]/route.ts
ts
export async function DELETE(_: NextRequest, { params }: { params: { id: string } }) {
await prisma.post.delete({ where: { id: Number(params.id) } });
return NextResponse.json({ ok: true });
}
前端点击"删除"按钮触发:
ts
await fetch(`/api/posts/${id}`, { method: 'DELETE' });
小图标:🗑️ "数据谢幕,观众请保持安静 404"
7️⃣ 彩蛋:实时订阅,让数据库开麦
Prisma 的实时功能(预览):
ts
const stream = prisma.post.subscribe(); // 伪代码
下一季音乐剧:WebSocket + Server Sent Events + Prisma Pulse,敬请期待 🍿
8️⃣ 谢幕清单:部署走你
-
推代码到 GitHub
-
一键 Vercel:选 Postgres 模板,自动注入
DATABASE_URL
-
看日志:
bash[POST] /api/posts 201 23ms [GET] /api/posts 200 15ms
观众鼓掌:👏 "从本地 3000 到线上 HTTPS,只花了 30 秒"
9️⃣ 彩蛋台词墙
角色 | 经典台词 |
---|---|
Prisma Client | "我不是 ORM,我是类型安全的舞伴。" |
Next.js | "App Router:服务端组件,免费 SSR,不加价。" |
PostgreSQL | "索引就像高跟鞋,漂亮但别乱穿。" |
"愿你每次
npx prisma migrate
都能顺利通过,愿每个
404
都变成200 OK
。"------ 一名把 SQL 跳成芭蕾舞的全栈工程师 🩰