Prisma is an open-source next-generation ORM. It consists of the following parts: Prisma Client: Auto-generated and type-safe query builder
https://www.prisma.io
Install prisma/client
npm install @prisma/client
https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client
Generate Prisma Client with the following command:
npx prisma generate
Install prisma
npm install prisma --save-dev
https://www.prisma.io/docs/getting-started/quickstart
set up Prisma with the init command of the Prisma CLI:
npx prisma init --datasource-provider sqlite
to npx prisma init --datasource-provider mysql
Model data in the Prisma schema
prisma/schema.prisma
model Todo {
id Int @id @default(autoincrement())
title String?
isCompleted Boolean @default(false)
updatedAt DateTime? @updatedAt
createdAt DateTime @default(now())
}
Run a migration to create your database tables with Prisma Migrate
npx prisma migrate dev --name init
.env
DATABASE_URL="mysql://root:root@localhost:8889/nextjsdb"
https://www.prisma.io/docs/orm/prisma-client/queries/crud
https://nextjs.org/docs/app/building-your-application/routing/route-handlers
app/page.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //app/page.tsx import { PrismaClient } from "@prisma/client" ; import AddTodo from "@/components/AddTodo" ; import Todo from "@/components/Todo" ; const prisma = new PrismaClient(); async function getData() { const data = await prisma.todo.findMany({ select: { title: true, id: true, isCompleted: true, }, orderBy: { createdAt: "desc" , }, }); return data; } const Home = async () => { const data = await getData(); return ( <div className= "w-screen py-20 flex justify-center flex-col items-center" > <h1 className= " text-3xl font-extrabold mb-5" > Next.js 14 Todo Server Actions </h1> <div className= "flex justify-center flex-col items-center w-[1000px] " > <AddTodo /> <div className= " flex flex-col gap-5 items-center justify-center mt-10 w-full" > {data.map((todo, id) => ( <div className= "w-full card bg-base-100 shadow-xl" key={id}> <div className= "card-body" > <Todo todo={todo} /> </div> </div> ))} </div> </div> </div> ); }; export default Home; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | //app/actions/route.ts "use server" ; import { revalidatePath } from "next/cache" ; import { prisma } from "@/utils/prisma" ; export async function create(formData: FormData) { const input = formData.get( "input" ) as string; if (!input.trim()) { return ; } await prisma.todo.create({ data: { title: input, }, }); revalidatePath( "/" ); } export async function edit(formData: FormData) { const input = formData.get( "newTitle" ) as string; const inputId = formData.get( "inputId" ) as string; const id = parseInt(inputId); await prisma.todo.update({ where: { id: id, }, data: { title: input, }, }); revalidatePath( "/" ); } export async function deleteTodo(formData: FormData) { const inputId = formData.get( "inputId" ) as string; const id = parseInt(inputId); await prisma.todo. delete ({ where: { id: id, }, }); revalidatePath( "/" ); } export async function todoStatus(formData: FormData) { const inputId = formData.get( "inputId" ) as string; const id = parseInt(inputId); const todo = await prisma.todo.findUnique({ where: { id: id, }, }); if (!todo) { return ; } const updatedStatus = !todo.isCompleted; await prisma.todo.update({ where: { id: id, }, data: { isCompleted: updatedStatus, }, }); revalidatePath( "/" ); return updatedStatus; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //components\AddTodo.tsx import { create } from "@/app/actions/route" ; import Form from "@/components/Form" ; const AddTodo = () => { return ( <Form action={create} className= "w-1/2 m-auto" > <div className= "flex" > <input type= "text" name= "input" placeholder= "Add Todo..." className= "input input-bordered input-success w-full max-w-xs" /> <button className= "btn btn-primary" >Add New Todo</button> </div> </Form> ); }; export default AddTodo; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //components\ChangeTodo.tsx import { todoStatus } from "@/app/actions/route" ; import Form from "@/components/Form" ; import { todoType } from "@/types/todoTypes" ; const ChangeTodo = ({ todo }: { todo: todoType }) => { return ( <Form action={todoStatus}> <input name= "inputId" value={todo.id} className= "border-gray-700 border" type= "hidden" /> <button type= "submit" className= "btn btn-info" >Done</button> </Form> ); }; export default ChangeTodo; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //components\DeleteTodo.tsx "use client" ; import { deleteTodo } from "@/app/actions/route" ; import Form from "@/components/Form" ; import { todoType } from "@/types/todoTypes" ; const DeleteTodo = ({ todo }: { todo: todoType }) => { return ( <Form action={deleteTodo}> <input type= "hidden" name= "inputId" value={todo.id} /> <button type= "submit" className= "btn btn-error" > Delete </button> </Form> ); }; export default DeleteTodo; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | //components\EditTodo.tsx "use client" ; import { edit } from "@/app/actions/route" ; import Form from "@/components/Form" ; import { useState } from "react" ; import { todoType } from "@/types/todoTypes" ; const EditTodo = ({ todo }: { todo: todoType }) => { const [editTodo, setEditTodo] = useState(false); const handleEdit = () => { setEditTodo(!editTodo); }; const handleSubmit = () => { setEditTodo(false); }; return ( <div className= "flex gap-5 items-center" > <button onClick={handleEdit} className= "btn btn-success" >Edit </button> {editTodo ? ( <Form action={edit} onSubmit={handleSubmit}> <input name= "inputId" value={todo.id} type= "hidden" /> <div className= "flex justify-center" > <input type= "text" placeholder= "Edit Todo..." name= "newTitle" className= "input input-bordered input-success w-full max-w-xs" /> <button type= "submit" className= "btn btn-success" >Save </button> </div> </Form> ) : null} </div> ); }; export default EditTodo; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //components\Form.tsx "use client" ; import { useRef } from "react" ; import { ReactNode } from "react" ; interface FormProps { children: ReactNode; action: (formData: FormData) => Promise<void | boolean>; className?: string; onSubmit?: () => void; } const Form = ({ children, action, className, onSubmit, }: FormProps) => { const ref = useRef<HTMLFormElement>(null); return ( <form className={className} onSubmit={onSubmit} ref={ref} action={async (formData) => { await action(formData); ref.current?.reset(); }} > {children} </form> ); }; export default Form; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //components\Todo.tsx import ChangeTodo from "./ChangeTodo" ; import EditTodo from "./EditTodo" ; import DeleteTodo from "./DeleteTodo" ; import { todoType } from "@/types/todoTypes" ; const Todo = ({ todo }: { todo: todoType }) => { const todoStyle = { textDecoration: todo.isCompleted === true ? "line-through" : "none" , opacity: todo.isCompleted === true ? 0.5 : 1, }; return ( <div className= "w-full flex items-center justify-between py-3 px-20 rounded-2xl" style={todoStyle} > <h2 className= "card-title" >{todo.title}</h2> <div className= "flex items-center gap-5" > <ChangeTodo todo={todo} /> <EditTodo todo={todo} /> <DeleteTodo todo={todo} /> </div> </div> ); }; export default Todo; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" } model Todo { id Int @id @ default (autoincrement()) title String? isCompleted Boolean @ default (false) updatedAt DateTime? @updatedAt createdAt DateTime @ default (now()) } |
1 2 3 4 5 6 7 8 | //types\todoTypes.ts export type todoType = { id: string; title?: string | null; isCompleted: boolean; updatedAt?: Date | null; createdAt?: Date ; }; |
1 2 3 4 5 6 7 8 9 | 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 |