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 = prisma
run C:\nextjs>npm run dev
