🎬《Next 全栈 CRUD 的百老汇》

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

这两行命令做了什么?

  1. migrate:把 schema 翻译成 SQL,像同声传译。
  2. 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️⃣ 谢幕清单:部署走你

  1. 推代码到 GitHub

  2. 一键 Vercel:选 Postgres 模板,自动注入 DATABASE_URL

  3. 看日志:

    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 跳成芭蕾舞的全栈工程师 🩰

相关推荐
IT_陈寒1 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端
kyriewen1 小时前
我让AI改一个bug——它偷偷动了5个我没让它碰的地方
前端·javascript·ai编程
段一凡-华北理工大学1 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
遇乐的果园2 小时前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
码事漫谈2 小时前
1999年的电商前夜和2026年的AI前夜 历史押韵但从不重复
后端
Conan在掘金2 小时前
鸿蒙报错速查:arkts-strict-typing Property does not exist on type 'object',object 装函数就炸,根因 + 真解法
后端
用户985033593282 小时前
OpenLayers 热力图从原理到 Vue 组件一键接入
前端
颜酱2 小时前
01 | 骨架搭建:FastAPI + Vue 跑通第一个 SSE 流式问答
前端·人工智能·后端
hoLzwEge2 小时前
解码 IDE 智能提示:jsconfig.json 辅助开发全攻略
前端·前端框架
leoZ2312 小时前
记忆系统与 Agent 定制完全指南(四):自定义 Agent 开发(一)
前端·chrome