You can connect Drizzle ORM and Prisma to your Next.js app and use them both, but it’s not a free lunch.
Let’s see Drizzle in action with a simple Next.js API route. Imagine we have a users table.
// db/schema.ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow(),
});
Here’s how you’d query it in an API route:
// app/api/users/route.ts
import { sql } from '@vercel/postgres';
import { drizzle } from 'drizzle-orm/vercel-postgres';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';
export async function GET() {
const db = drizzle(sql);
const allUsers = await db.select().from(users);
return Response.json(allUsers);
}
export async function POST(request: Request) {
const { name, email } = await request.json();
const db = drizzle(sql);
await db.insert(users).values({ name, email });
return Response.json({ message: 'User created' });
}
Now, let’s layer in Prisma. Prisma’s strength is its auto-generated client and migrations. You’d define your schema like this:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}
And then use the Prisma client in your Next.js app:
// app/users/page.tsx (example client component)
'use client';
import { useState, useEffect } from 'react';
import { PrismaClient } from '@prisma/client'; // Note: This is conceptual for client-side; usually server-side
const prisma = new PrismaClient(); // In a real Next.js app, this would be on the server
async function getUsers() {
// In a real Next.js app, this function would be in an API route or server component
const users = await prisma.user.findMany();
return users;
}
export default function UsersPage() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function fetchUsers() {
const fetchedUsers = await getUsers(); // This call needs to be to an API route
setUsers(fetchedUsers);
}
fetchUsers();
}, []);
return (
<div>
<h1>Users</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
The core problem Drizzle solves is providing a type-safe SQL builder that’s incredibly fast and lightweight, especially when you’re already working with a specific database driver like pg or @vercel/postgres. Prisma, on the other hand, offers a higher-level abstraction with automatic migrations, a powerful schema definition language, and a robust client that handles connections and pooling for you.
When you integrate them, you’re essentially choosing which tool to use for which job. A common pattern is to use Drizzle for your core database interactions where performance and direct SQL control are paramount, and then use Prisma for its migration capabilities and potentially for specific, simpler CRUD operations where the auto-generated client is convenient.
Here’s a more concrete example of how you might use them together. Suppose you want to manage your database schema with Prisma Migrations but use Drizzle for your API routes.
-
Set up Prisma:
- Run
npm install prisma --save-dev - Run
npx prisma init - Configure
prisma/schema.prismawith your database connection string. - Define your
Usermodel as shown above. - Run
npx prisma migrate dev --name initto create your tables.
- Run
-
Set up Drizzle:
- Run
npm install drizzle-orm pg @vercel/postgres(or your chosen DB driver). - Define your Drizzle schema (
db/schema.ts) to match your Prisma schema. This is crucial for type safety. - In your API routes or server components, initialize Drizzle using the same database connection that Prisma uses. For
@vercel/postgres, this would look like:
// lib/db.ts import { neon } from '@vercel/postgres'; import { drizzle } from 'drizzle-orm/vercel-postgres'; const queryClient = neon(process.env.POSTGRES_URL!); export const db = drizzle(queryClient);Then use
dbfromlib/db.tsin your API routes. - Run
The most surprising true thing about this topic is that you can, and often should, run both Drizzle and Prisma in the same Next.js application. Prisma handles the schema definition, migrations, and provides a client for easy access to your data. Drizzle, with its SQL-like syntax and minimal overhead, can be used for performance-critical queries or when you need more direct control over the SQL being executed, all while benefiting from the type safety provided by your shared schema definition.
The key to making this work is ensuring your Drizzle schema precisely mirrors your Prisma schema. Any discrepancy will lead to runtime errors or unexpected behavior. Use Prisma’s db push or migrate dev to manage the actual database structure, and then use that structure as the source of truth for your Drizzle schema definition.
When you are using Drizzle with @vercel/postgres and encounter a query that seems to be very slow, even though the SQL looks fine, it’s often because Drizzle is generating a query that the PostgreSQL planner isn’t optimizing well for your specific data distribution. In such cases, switching to a raw SQL query using sql from @vercel/postgres and then manually passing that to Drizzle’s db.execute() or db.query() can sometimes yield better performance, as you’re bypassing Drizzle’s query builder entirely and letting the database handle the execution plan.
The next logical step is exploring advanced Drizzle features like migrations or integrating a connection pool for better performance in serverless environments.