Implementing MVC with Next.js and Prisma
Date Published

Summary
M : Model = Prisma (data + repositories)
V : React = Server/client components
C : Controller = Route handlers + server actions
Why ? Keep your application simple and scalable
For this we'll use an example application
Application architecture
1. Usecase and class diagram


2. Components diagram

The plantuml source of this diagram you can find here
What does MVC have to do with it ?
1. the M stands for model
This handles the DB configuration and the DB access via Prisma
1generator client {2 provider = "prisma-client-js"3 output = "../src/generated/prisma"4}56// prisma/schema.prisma7datasource db {8 provider = "postgresql"9 url = env("DATABASE_URL")10}1112model User {13 id String @id @default(cuid())14 email String @unique15 hashedPassword String16 posts Post[] // One User can have many Posts17}1819model Post {20 id String @id @default(cuid())21 title String22 slug String @unique23 content String24 published Boolean? @default(false)25 imageUrl String?26 author User @relation(fields: [authorId], references: [id]) // Each Post belongs to one User27 authorId String28 updatedAt DateTime @updatedAt29 createdAt DateTime @default(now())3031 @@index(slug)32}
Two models are defined : User and Post
2. The Controller part
In the controller layer, you define the actions (use cases) the application must perform.
In this application we have three actions :
createPost, editPost, deletePost : In our case, those also correspond to the features (use-cases) of the use-case diagram
in the componentsdiagram you will them see too at the bottom.
createPostCode :
1'use server';23import prisma from '@/lib/db';4import { revalidatePath } from 'next/cache';5import { Prisma } from '@/generated/prisma';67export async function createPost(formData: FormData) {8 try {9 await prisma.post.create({10 data: {11 title: formData.get('title') as string,12 slug: (formData.get('title') as string)13 .replace(/\s+/g, '-')14 .toLowerCase(),15 content: formData.get('content') as string,16 author: {17 connect: {18 email: 'john@gmail.com',19 },20 },21 },22 });23 } catch (error) {24 if (error instanceof Prisma.PrismaClientKnownRequestError) {25 console.log('error code :', error.code);26 if (error.code === 'P2002') {27 console.log(28 'There is a unique constraint violation, a new user cannot be created with this email'29 );30 }31 }32 // console.error(error);33 }34 revalidatePath('/posts');
You see that the controller uses prisma (the model) to access the database.
3. The view part
This is the user interface of the application used by our actors (in the usecase diagram).
Those are the jsx components of our application. In the components diagram you see that the createPostForm component will use the createPost Action (or controller) to create a post.
createPostForm code
1import { createPost } from '@/actions/actions';23export default function CreatePostForm() {4 return (5 <form6 action={createPost}7 className="flex flex-col gap-y-3 w-[300px] bg-white p-6 rounded-lg shadow-sm"8 >9 <input10 type="text"11 name="title"12 placeholder="Title"13 className="px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"14 required15 />16 <textarea17 name="content"18 rows={5}19 placeholder="Content"20 className="px-3 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"21 required22 />23 <button24 type="submit"25 className="bg-blue-600 hover:bg-blue-700 py-2 text-white rounded-md font-medium transition-colors"26 >27 Create Post28 </button>29 </form>30 );31}
4. This post is inspired by this nice video
thank you bytegrad !

Learn how PlantUML helps developers create clear, efficient application architecture diagrams for better design and communication.