NextJS+Prisma项目中后端概念是否消失?如何实现MVC架构?
NextJS + Prisma 场景下的后端概念与MVC架构实现
后端概念并未消失
NextJS是全栈框架,并非消除了后端概念,只是将前后端逻辑整合在同一个项目中。你依然需要处理数据校验、业务逻辑、数据库交互等后端工作,NextJS提供了多种承载后端逻辑的方式:
- API路由:独立的后端接口,和传统RESTful API逻辑一致
- Server Components:在服务器端运行的组件,可直接执行数据库查询等后端操作
- Server Actions:App Router中推荐的异步数据处理方式,用于表单提交、数据修改等场景
这些方式本质都是后端逻辑的实现,只是不需要单独搭建和部署后端服务,由NextJS统一处理服务器端运行。
基于NextJS + Prisma 实现MVC架构
下面结合SQL/NoSQL数据库(Prisma支持PostgreSQL、MySQL、MongoDB等),拆解MVC各层的实现方式:
1. Model层:由Prisma定义与实现
Model层负责数据结构定义和数据库交互,完全由Prisma承担:
- 首先在
schema.prisma中定义数据模型,对应数据库表/集合:
// schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" // 可替换为mysql、mongodb等 url = env("DATABASE_URL") } model Post { id Int @id @default(autoincrement()) title String @db.VarChar(255) content String? author String @db.VarChar(100) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
- 运行
npx prisma generate生成Prisma Client,这是Model层的核心工具,通过它执行CRUD操作,无需手动编写SQL/NoSQL语句。
2. Controller层:处理业务逻辑
Controller层负责接收请求、校验参数、调用Model层、处理业务规则,可根据场景选择以下实现方式:
方式一:API路由(Pages/App Router均支持)
适合需要对外提供RESTful接口的场景,比如供客户端组件调用或第三方系统集成:
// app/api/posts/route.js import prisma from '@/lib/prisma'; // 获取文章列表 export async function GET() { const posts = await prisma.post.findMany({ orderBy: { createdAt: 'desc' }, select: { id: true, title: true, author: true, createdAt: true } }); return Response.json(posts); } // 创建文章 export async function POST(request) { try { const { title, content, author } = await request.json(); // 参数校验 if (!title || !author) { return Response.json({ error: '标题和作者不能为空' }, { status: 400 }); } const post = await prisma.post.create({ data: { title, content, author } }); return Response.json(post, { status: 201 }); } catch (error) { return Response.json({ error: '创建失败' }, { status: 500 }); } }
方式二:Server Actions(App Router推荐)
适合处理表单提交、页面内数据操作等场景,无需单独编写API接口:
// app/actions/postActions.js 'use server'; import prisma from '@/lib/prisma'; export async function createPost(formData) { const title = formData.get('title'); const content = formData.get('content'); const author = formData.get('author'); if (!title || !author) { throw new Error('标题和作者为必填项'); } return await prisma.post.create({ data: { title, content, author } }); } export async function deletePost(postId) { return await prisma.post.delete({ where: { id: parseInt(postId) } }); }
方式三:Service层(逻辑抽离)
如果业务逻辑复杂,可将Controller逻辑抽离为独立的service文件,保持代码整洁:
// app/services/postService.js import prisma from '@/lib/prisma'; export async function getPostById(postId) { return await prisma.post.findUnique({ where: { id: parseInt(postId) } }); } export async function updatePost(postId, data) { return await prisma.post.update({ where: { id: parseInt(postId) }, data: { ...data } }); }
3. View层:NextJS页面与组件
View层负责渲染UI,展示Controller层返回的数据,分为两种类型:
- Server Components:直接在服务器端获取数据并渲染,无需客户端请求,适合静态内容或数据敏感页面:
// app/posts/page.js import { getAllPosts } from '@/services/postService'; export default async function PostsPage() { const posts = await getAllPosts(); return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">博客文章</h1> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> {posts.map(post => ( <div key={post.id} className="border p-4 rounded"> <h2 className="text-xl font-semibold">{post.title}</h2> <p className="text-gray-600 mt-1">作者:{post.author}</p> <p className="text-sm text-gray-500 mt-2">{post.createdAt.toLocaleString()}</p> </div> ))} </div> </div> ); }
- Client Components:在客户端运行,可通过API请求或调用Server Actions与后端交互,适合需要交互的动态页面:
// app/posts/create/page.js 'use client'; import { createPost } from '@/actions/postActions'; import { useRouter } from 'next/navigation'; export default function CreatePostPage() { const router = useRouter(); async function handleSubmit(formData) { try { await createPost(formData); router.push('/posts'); } catch (error) { alert(error.message); } } return ( <div className="container mx-auto p-4 max-w-md"> <h1 className="text-2xl font-bold mb-4">发布新文章</h1> <form action={handleSubmit} className="flex flex-col gap-3"> <input type="text" name="title" placeholder="文章标题" className="border p-2 rounded" required /> <input type="text" name="author" placeholder="作者" className="border p-2 rounded" required /> <textarea name="content" placeholder="文章内容" rows={5} className="border p-2 rounded" /> <button type="submit" className="bg-blue-500 text-white p-2 rounded hover:bg-blue-600"> 发布 </button> </form> </div> ); }
内容的提问来源于stack exchange,提问作者JustDev
相关产品推荐
相关产品推荐

