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
//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;app/actions/route.ts
//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; }components\AddTodo.tsx
//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;components\ChangeTodo.tsx
//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;components\DeleteTodo.tsx
//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;components\EditTodo.tsx
//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;components\Form.tsx
//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;components\Todo.tsx
//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;prisma\schema.prisma
// 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" url = "mysql://root:root@localhost:8889/nextjsdb" } model Todo { id Int @id @default(autoincrement()) title String? isCompleted Boolean @default(false) updatedAt DateTime? @updatedAt createdAt DateTime @default(now()) }types\todoTypes.ts
//types\todoTypes.ts export type todoType = { id: string; title?: string | null; isCompleted: boolean; updatedAt?: Date | null; createdAt?: Date; };utils\prisma.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 = prismarun C:\nextjs>npm run dev