Ir al contenido
angel.library

CRUD con Prisma en Next.js

repositories/posts.repository.ts
import { prisma } from "@/libs/prisma"
export const postsRepository = {
findAll: () => prisma.post.findMany(),
findById: (id: string) => prisma.post.findUnique({ where: { id } }),
create: (data: { title: string; authorId: string }) =>
prisma.post.create({ data }),
update: (id: string, data: { title?: string }) =>
prisma.post.update({ where: { id }, data }),
delete: (id: string) => prisma.post.delete({ where: { id } })
}
app/api/posts/route.ts
import { NextResponse } from "next/server"
import { postsRepository } from "@/repositories/posts.repository"
export async function GET() {
const posts = await postsRepository.findAll()
return NextResponse.json(posts)
}

Crear/actualizar/borrar: Server Actions con revalidatePath

Sección titulada «Crear/actualizar/borrar: Server Actions con revalidatePath»
app/actions/posts.ts
"use server"
import { revalidatePath } from "next/cache"
import { auth } from "@/libs/auth"
import { postsRepository } from "@/repositories/posts.repository"
export async function crearPost(formData: FormData) {
const session = await auth()
if (!session?.user) throw new Error("No autenticado")
await postsRepository.create({
title: formData.get("title") as string,
authorId: session.user.id
})
revalidatePath("/posts") // la página de listado se refresca con el dato nuevo
}
export async function eliminarPost(id: string) {
await postsRepository.delete(id)
revalidatePath("/posts")
}

revalidatePath es lo que hace que la Server Component de listado (/posts) muestre el dato actualizado en la siguiente navegación/render, sin necesitar un fetch manual desde el cliente ni recargar toda la página — ver Revalidación de paths para el mecanismo completo.

Consumir desde un Server Component + formulario

Sección titulada «Consumir desde un Server Component + formulario»
app/posts/page.tsx
import { crearPost, eliminarPost } from "@/app/actions/posts"
import { postsRepository } from "@/repositories/posts.repository"
export default async function PostsPage() {
const posts = await postsRepository.findAll()
return (
<>
<form action={crearPost}>
<input
name="title"
placeholder="Título"
required
/>
<button type="submit">Crear</button>
</form>
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title}
<form action={eliminarPost.bind(null, post.id)}>
<button type="submit">Eliminar</button>
</form>
</li>
))}
</ul>
</>
)
}

eliminarPost.bind(null, post.id) pre-carga el id como primer argumento de la action — el patrón estándar para pasarle un valor fijo a una Server Action desde un form action, que solo puede invocarla con el FormData del form.

  • Sin revalidatePath (o revalidateTag), la página de listado seguiría mostrando datos cacheados/viejos después de crear o eliminar un post — es un paso fácil de olvidar y la causa más común de “creé el post pero no aparece en la lista”.
  • Este mismo CRUD también podría exponerse completo como Route Handlers (POST/PATCH/DELETE en app/api/posts/route.ts) si algo externo a la propia app necesita consumirlo — ver API REST con Route Handlers.