DEV Community

Cover image for Build a Next.js App with Prisma Postgres
Aidan McAlister
Aidan McAlister

Posted on

Build a Next.js App with Prisma Postgres

Introduction

This guide walks through building a Next.js 15 app with Prisma Postgres as the database. Prisma Postgres is a fully managed, serverless Postgres that integrates directly with the Prisma ORM. No separate hosting or setup needed.

You’ll connect Next.js to Prisma Postgres, run migrations, seed data, query with Prisma Client, and deploy to Vercel.

Full example here: GitHub repo.

Prerequisites

  • Node.js 18+
  • A Vercel account (for deployment)

1) Create the app

First, scaffold a new Next.js 15 project.

npx create-next-app@latest nextjs-prisma
cd nextjs-prisma
Enter fullscreen mode Exit fullscreen mode

This creates a new project folder with Next.js defaults. Choose TypeScript, ESLint, Tailwind, and the App Router.

2) Install Prisma and init Prisma Postgres

Next, install Prisma and set it up with Prisma Postgres.

npm i -D prisma tsx
npm i @prisma/client @prisma/extension-accelerate
npx prisma init --db --output ../app/generated/prisma
Enter fullscreen mode Exit fullscreen mode

During init, you’ll be asked to create a Prisma Postgres database. Pick a region close to your users and give it a name.

This step gives you:

  • a prisma/schema.prisma file
  • a Prisma Postgres database provisioned automatically
  • .env with DATABASE_URL prefilled
  • a generated Prisma Client in app/generated/prisma

If you’re using your own Postgres database, you can skip @prisma/extension-accelerate and provide your own DATABASE_URL.

3) Define the schema

Now define your models in the Prisma schema.

generator client {
  provider = "prisma-client"
  output   = "../app/generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
}
Enter fullscreen mode Exit fullscreen mode

This creates a User model with a one-to-many relationship to Post.

Apply the schema to your Prisma Postgres database:

npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

This runs a migration and creates the corresponding tables inside Prisma Postgres.

4) Seed data

To test your app, add some initial users and posts. Create prisma/seed.ts:

import { PrismaClient } from "../app/generated/prisma/client";
const prisma = new PrismaClient();

async function main() {
  await prisma.user.create({
    data: {
      name: "Alice",
      email: "alice@prisma.io",
      posts: {
        create: [
          { title: "Join the Prisma Discord", content: "<https://pris.ly/discord>", published: true },
          { title: "Prisma on YouTube", content: "<https://pris.ly/youtube>" }
        ]
      }
    }
  });
  await prisma.user.create({
    data: {
      name: "Bob",
      email: "bob@prisma.io",
      posts: {
        create: [{ title: "Follow Prisma on Twitter", content: "<https://twitter.com/prisma>", published: true }]
      }
    }
  });
}

main();
Enter fullscreen mode Exit fullscreen mode

Tell Prisma how to run the seed script by adding to package.json:

{
  "prisma": { "seed": "tsx prisma/seed.ts" }
}
Enter fullscreen mode Exit fullscreen mode

Run the seed and open Prisma Studio to view data in Prisma Postgres:

npx prisma db seed
npx prisma studio
Enter fullscreen mode Exit fullscreen mode

5) Create a single Prisma Client

Next.js hot reload can create multiple Prisma clients, which can exhaust connections. To prevent that, create a single shared client in lib/prisma.ts:

import { PrismaClient } from "../app/generated/prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";

const globalForPrisma = global as unknown as { prisma?: PrismaClient };

const prisma =
  globalForPrisma.prisma ??
  new PrismaClient().$extends(withAccelerate());

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

export default prisma;
Enter fullscreen mode Exit fullscreen mode

This reuses the client in dev. In production, Prisma Postgres handles pooling and scaling automatically.

6) Read data in Server Components

With Prisma Client ready, query Prisma Postgres directly inside async Server Components.

Example home page showing all users:

// app/page.tsx
import prisma from "@/lib/prisma";

export default async function Home() {
  const users = await prisma.user.findMany();
  return (
    <div className="min-h-screen flex flex-col items-center justify-center">
      <h1 className="text-4xl font-bold mb-8">Superblog</h1>
      <ol className="list-decimal">
        {users.map(u => <li key={u.id}>{u.name}</li>)}
      </ol>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Posts page including authors:

// app/posts/page.tsx
import prisma from "@/lib/prisma";

export default async function Posts() {
  const posts = await prisma.post.findMany({ include: { author: true } });
  return (
    <div className="min-h-screen flex flex-col items-center justify-center">
      <h1 className="text-4xl font-bold mb-8">Posts</h1>
      <ul className="space-y-4">
        {posts.map(p => (
          <li key={p.id}>
            <strong>{p.title}</strong> by {p.author?.name ?? "Anonymous"}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Dynamic post detail route:

// app/posts/[id]/page.tsx
import prisma from "@/lib/prisma";
import { notFound } from "next/navigation";

export default async function Post({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await prisma.post.findUnique({
    where: { id: Number(id) },
    include: { author: true }
  });
  if (!post) notFound();

  return (
    <article className="max-w-2xl mx-auto p-6">
      <h1 className="text-3xl font-bold">{post.title}</h1>
      <p className="text-gray-600">by {post.author?.name ?? "Anonymous"}</p>
      <div className="mt-6">{post.content || "No content available."}</div>
    </article>
  );
}
Enter fullscreen mode Exit fullscreen mode

7) Create data with Server Actions

Now add a form that writes to Prisma Postgres using a Server Action.

// app/posts/new/page.tsx
import Form from "next/form";
import prisma from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export default function NewPost() {
  async function createPost(formData: FormData) {
    "use server";
    const title = String(formData.get("title") || "");
    const content = String(formData.get("content") || "");
    await prisma.post.create({ data: { title, content, authorId: 1 } });
    revalidatePath("/posts");
    redirect("/posts");
  }

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h1 className="text-2xl font-bold mb-6">Create New Post</h1>
      <Form action={createPost} className="space-y-4">
        <input name="title" placeholder="Title" className="w-full border px-3 py-2 rounded" />
        <textarea name="content" placeholder="Content" rows={6} className="w-full border px-3 py-2 rounded" />
        <button type="submit" className="w-full bg-blue-600 text-white py-2 rounded">Create Post</button>
      </Form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Server Actions run directly against Prisma Postgres, with no need for API routes.

8) Dev notes

  • On Next.js 15.2.0 or 15.2.1, avoid Turbopack (bug). Change your package.json dev script to "dev": "next dev".
  • Prisma Studio is handy for quickly viewing your Prisma Postgres data.

9) Deploy to Vercel

Finally, deploy the app with Prisma Postgres as the backing DB.

npm i -g vercel
vercel login
Enter fullscreen mode Exit fullscreen mode

Update package.json so Prisma Client is generated on install:

{
  "scripts": {
    "postinstall": "prisma generate --no-engine"
  }
}
Enter fullscreen mode Exit fullscreen mode

If you’re not using Prisma Postgres, remove --no-engine.

Deploy:

vercel
Enter fullscreen mode Exit fullscreen mode

Vercel builds and deploys your app, while Prisma Postgres provides the globally available database — no extra hosting needed.

Docs for further reading:

Top comments (0)