Next.js + Prisma + PostgreSQL
Configuración full-stack de Next.js 15 con Prisma ORM, base de datos PostgreSQL y Server Actions para mutaciones.
Busca en todas las páginas de la documentación
Configuración full-stack de Next.js 15 con Prisma ORM, base de datos PostgreSQL y Server Actions para mutaciones.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-appnpm install prisma @prisma/client
npm install -D prismanpx prisma init --datasource-provider postgresqlprisma/schema.prisma.DATABASE_URL en .env.npx prisma migrate dev --name initlib/prisma.ts para evitar agotar conexiones durante la recarga en caliente.Una aplicación Todo completa con Prisma + PostgreSQL + Server Actions.
prisma/schema.prismagenerator client \{
provider = "prisma-client-js"
\}
datasource db \{
provider = "postgresql"
url = env("DATABASE_URL")
\}
model Todo \{
id String @id @default(cuid())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
\}lib/prisma.ts (singleton)import \{ PrismaClient \} from "@prisma/client";
const globalForPrisma = globalThis as unknown as \{
prisma: PrismaClient | undefined;
\};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient(\{
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
\});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;app/page.tsx (Server Component)import \{ prisma \} from "@/lib/prisma";
import \{ addTodo \} from "./actions";
export default async function Home() \{
const todos = await prisma.todo.findMany(\{
orderBy: \{ createdAt: "desc" \},
\});
return (
<main className="mx-auto max-w-xl p-8">
<h1 className="text-2xl font-bold">Todos</h1>
<form action=\{addTodo\} className="mt-4 flex gap-2">
<input
name="title"
required
className="flex-1 rounded border px-3 py-2"
placeholder="Nuevo todo..."
/>
<button type="submit" className="rounded bg-black px-4 py-2 text-white">
Agregar
</button>
</form>
<ul className="mt-6 space-y-2">
\{todos.map((todo) => (
<li key=\{todo.id\} className="rounded border p-3">
\{todo.title\}
</li>
))\}
</ul>
</main>
);
\}app/actions.ts (Server Action)"use server";
import \{ prisma \} from "@/lib/prisma";
import \{ revalidatePath \} from "next/cache";
export async function addTodo(formData: FormData) \{
const title = formData.get("title");
if (typeof title !== "string" || title.trim() === "") return;
await prisma.todo.create(\{
data: \{ title: title.trim() \},
\});
revalidatePath("/");
\}Prisma genera un cliente completamente tipificado basado en tu esquema. El Server Component de Next.js se ejecuta en el servidor y consulta Postgres directamente a través del cliente Prisma. Las Server Actions son puntos finales POST creados automáticamente por Next.js - el envío de formularios invoca la acción, que muta la base de datos y llama a revalidatePath para purgar el render almacenado en caché para que los nuevos datos aparezcan en la siguiente solicitud.
El patrón singleton es crítico en desarrollo. La recarga en caliente de Next.js re-evalúa módulos en cada cambio, y sin un singleton crearías un nuevo PrismaClient por recarga, agotando rápidamente el conjunto de conexiones de la base de datos.
DATABASE_URL.provider = "sqlite" y usa url = "file:./dev.db" para una base de datos local sin configuración.prisma/seed.ts y regístralo bajo "prisma": \{ "seed": "tsx prisma/seed.ts" \} en package.json, luego ejecuta npx prisma db seed.npx prisma studio para un navegador de datos y editor basado en navegador.prisma://, ideal para despliegues serverless.import \{ Todo \} from "@prisma/client".Prisma.TodoGetPayload<\{ include: \{ author: true \} \}> para derivar un tipo exacto de la forma de retorno.(formData: FormData) => Promise<void> o usar el patrón más reciente useActionState con un argumento prevState.tsconfig.json para detectar campos anulables - Prisma respeta ? en esquema como T | null en TS.globalThis en lib/prisma.ts, el modo dev de Next.js filtrará conexiones hasta que Postgres rechace las nuevas.DATABASE_URL faltante. Prisma no generará sin ella. Colócala en .env (no solo .env.local) porque la CLI de Prisma lee .env.prisma generate. Después de cambiar schema.prisma, regenera el cliente o tus tipos se desviarán. prisma migrate dev lo hace automáticamente, pero prisma db push y ediciones manuales de esquema pueden no hacerlo.prisma migrate deploy en CI/CD, nunca prisma migrate dev - esto último puede solicitar interactivamente y eliminar datos.binaryTargets = ["native", "rhel-openssl-3.0.x"] al bloque generador, o tu despliegue fallará con "Binario de motor de consulta no encontrado".| Herramienta | Estilo | Mejor Para |
|---|---|---|
| Prisma | ORM con DSL de esquema | ORM completo tipificado, migraciones, Studio |
| Drizzle ORM | Constructor SQL tipificado | Ligero, amigable con Edge, más cercano a SQL |
| Kysely | Constructor de consultas tipificado | Equipos que quieren SQL-first con inferencia TS |
| postgres.js | Controlador SQL sin procesar | Control máximo, sin sobrecarga de abstracción |
| Cliente Supabase | SDK REST/Realtime | Auth + DB + realtime en un paquete |
El modo dev de Next.js recarga módulos en cambios de archivos. Cada recarga crea una nueva instancia de PrismaClient si ingenuamente haces new PrismaClient() en alcance de módulo, y cada instancia abre su propio conjunto de conexiones. En minutos alcanzas el límite de conexión de tu base de datos. El singleton globalThis sobrevive a la recarga en caliente porque vive en el objeto global, no en el módulo.
migrate dev genera archivos de migración SQL bajo prisma/migrations/ que confirmas en control de versiones - es el flujo seguro para producción. db push sincroniza el esquema directamente sin crear archivos de migración - úsalo solo para prototipado rápido.
No con el cliente estándar. Necesitas Prisma Accelerate (@prisma/extension-accelerate) o los Driver Adapters (actualmente en versión preliminar) que enrutan consultas a través de un proxy basado en fetch en lugar de TCP de Node.
Ejecuta npx prisma migrate deploy como parte de tu paso de compilación o lanzamiento. Esto aplica cualquier migración pendiente de prisma/migrations/ sin solicitar y sin generar nuevas.
Crea prisma/seed.ts, agrega "prisma": \{ "seed": "tsx prisma/seed.ts" \} a package.json, luego ejecuta npx prisma db seed. También se ejecuta automáticamente después de prisma migrate reset.
Agrega los binaryTargets correctos a schema.prisma:
generator client \{
provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-3.0.x"]
\}Luego confirma, redespliega y asegúrate de que prisma generate se ejecute en tu compilación ("postinstall": "prisma generate" en package.json es la solución estándar).
Ejecuta npx prisma generate manualmente. Es posible que el servidor TS de tu editor también necesite reiniciarse. Normalmente prisma migrate dev ejecuta generate para ti, pero db push y ediciones directas de esquema sin migración no.
Usa Prisma.<Model>GetPayload:
import \{ Prisma \} from "@prisma/client";
type TodoWithAuthor = Prisma.TodoGetPayload<\{
include: \{ author: true \};
\}>;Esto te da un tipo preciso que coincide exactamente con la forma que devuelve findMany(\{ include: \{ author: true \} \}).
Una acción simple toma FormData y devuelve Promise<void>:
export async function addTodo(formData: FormData): Promise<void> \{ /* ... */ \}Para useActionState agregas un parámetro prevState:
export async function addTodo(prevState: State, formData: FormData): Promise<State> \{ /* ... */ \}No. Prisma solo se ejecuta del lado del servidor. Llámalo desde Server Components, Server Actions, Route Handlers o generateStaticParams. Si un Client Component necesita datos, pásalos como props u obtén a través de una Server Action/Route Handler.
revalidatePath("/") le dice a Next.js que descarte el render almacenado en caché para esa ruta. En la siguiente solicitud, el Server Component se vuelve a ejecutar, consulta de nuevo a Prisma y obtiene los datos actualizados. Sin ella, los usuarios ven datos obsoletos hasta que expira el caché.
Prisma tiene mejor DX, Studio y migraciones. Drizzle es más ligero, nativo de Edge y te da sintaxis similar a SQL con tipos fuertes. Elige Prisma para la mayoría de aplicaciones full-stack; elige Drizzle si te importa el tamaño de paquete, compatibilidad de Edge o quieres mantenerte cerca de SQL.
Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥