Setting Up Drizzle ORM in a Next.js Project with PostgreSQL
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.