Setting Up Drizzle ORM in a Next.js Project with PostgreSQL

/Building

I have been using Prisma with Apollo Server and Client for my projects. Even for simple ones, I kept thinking that exposing GraphQL would only do good things because it would make life easier if I ever needed the same database for another web or app. Years passed. That moment never came.

I am tired of dealing with the slow, heavy feeling of my Next.js stack. So I dropped GraphQL completely and replaced Prisma with Drizzle. Here’s how I set up a fresh Next.js project using Drizzle (PostgreSQL).

1. Create a New Next.js Project

npx create-next-app@latest my-app
cd my-app

I choose to use the import aliases provided by Next.js, which are configured by default. This allows me to use @/ to reference the root of the project for cleaner and more manageable imports.

2. Install Drizzle and Dependencies

For PostgreSQL:

npm i drizzle-orm pg dotenv
npm i -D drizzle-kit tsx @types/pg

If you use another database, you can check out the documentation (https://orm.drizzle.team/docs/get-started).

3. Create a Database Connection

Inside app/lib/drizzle.ts:

import { drizzle } from "drizzle-orm/node-postgres"
import { Pool } from "pg"

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
})

export const db = drizzle({ client: pool })

Make sure .env.local contains:

DATABASE_URL=postgres://user:password@localhost:5432/mydb

4. Define Your Schema and Configure Drizzle Kit

Create a new folder:

drizzle/
schema.ts
drizzle.config.ts

Inside schema.ts:

import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: text("title").notNull(),
  createdAt: timestamp("created_at").defaultNow(),
});

Inside drizzle.config.ts:

import { config } from "dotenv"
import { defineConfig } from "drizzle-kit"

config({ path: `.env.local` })

export default defineConfig({
  out: `./drizzle/migrations`,
  schema: `./drizzle/schema.ts`,
  dialect: `postgresql`,
  dbCredentials: {
    url: process.env.DATABASE_URL!
  }
})

5. Create Helper Scripts

Open package.json and add these scripts:

"db:generate": "drizzle-kit generate --config=./drizzle/drizzle.config.ts",
"db:migrate": "drizzle-kit migrate --config=./drizzle/drizzle.config.ts",
"db:push": "drizzle-kit push --config=./drizzle/drizzle.config.ts",
"db:studio": "drizzle-kit studio --config=./drizzle/drizzle.config.ts",

6. Generate and Apply Migrations

Generate SQL:

npm run db:generate

Apply migrations:

npm run db:push

After pushing, your database now has the posts table.

Drizzle Studio gives a lightweight GUI to browse your tables.

npm run db:studio

Final Notes

The setup is completed. You can use Drizzle anywhere in your Next.js project, including in routes.

Example route: app/api/posts/route.ts

import { db } from "@/lib/drizzle";
import { posts } from "@/drizzle/schema";

export async function GET() {
  const data = await db.select().from(posts);
  return Response.json(data);
}

export async function POST(req: Request) {
  const { title } = await req.json();

  const row = await db.insert(posts).values({ title }).returning();

  return Response.json(row);
}

Drizzle doesn't generate client code like Prisma. You work directly with your schema.

  • Types come automatically from your table definitions.

  • Migrations are transparent: SQL files you can read and edit.

With this setup, you’re ready to build faster and more efficiently, without unnecessary complexity.