Topic 11 / 13

Working with Databases — Prisma

~10 min read  //  Next.js Series  //  Coding India

The Stack

Server components and actions can talk to a database directly — you just need an ORM. Prisma is the ecosystem favourite: schema-first, generated types, great DX:

npm install prisma @prisma/client
npx prisma init --datasource-provider sqlite   # postgres in production

Define the Schema

// prisma/schema.prisma
model Course {
  id        Int      @id @default(autoincrement())
  title     String
  slug      String   @unique
  price     Int
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String
  courses Course[]
}

Migrate

npx prisma migrate dev --name init    # creates SQL migration + applies it
npx prisma studio                     # GUI to browse your data

Migrations are versioned files in prisma/migrations/ — commit them, exactly like Django’s migrations.

The Client Singleton

Dev hot-reload re-imports modules; without a guard you leak connections:

// lib/db.js
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis;

export const db = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = db;
}

Queries — Fully Typed

import { db } from "@/lib/db";

// in a server component
const courses = await db.course.findMany({
  where: { published: true },
  orderBy: { createdAt: "desc" },
  include: { author: true },              // JOIN — typed result includes author
  take: 10,
});

const course = await db.course.findUnique({ where: { slug } });

// aggregate
const stats = await db.course.aggregate({
  _count: true,
  _avg: { price: true },
});
// in a server action
"use server";
export async function publishCourse(id) {
  const session = await auth();
  if (!session) throw new Error("unauthorised");

  await db.course.update({
    where: { id },
    data: { published: true },
  });
  revalidatePath("/courses");
}

Everything autocompletes and type-errors at build time — rename a field in the schema and TypeScript flags every stale query.

Production Notes

  • Use Postgres in production (Neon, Supabase, RDS); keep DATABASE_URL in env vars.
  • Serverless deployments need a connection-pooling URL (Neon/Supabase provide one) — each lambda would otherwise open its own connections.
  • Deploy schema changes with npx prisma migrate deploy in CI before the app starts.
  • DB queries aren’t fetch-cached — use route revalidate or unstable_cache (data-fetching topic) for hot reads.

Drizzle is the lighter SQL-flavoured alternative gaining ground; the architecture (schema → migrate → typed queries from server code) is identical.