Next.js Connecting to a Database
A database stores your application's data — user accounts, blog posts, orders, or any information that persists beyond a single page load. Next.js connects to databases entirely on the server. Your database credentials never reach the browser, and your database connection stays secure.
Where Database Code Lives
Database queries run only in server-side code. They belong in server components, server actions, and API routes — never in client components.
SERVER SIDE (safe for DB):
✓ Server Components (async page.js, layout.js)
✓ Server Actions ('use server' functions)
✓ API Routes (route.js files)
CLIENT SIDE (no DB access):
✗ Client Components ('use client')
✗ useEffect hooks
✗ Browser JavaScript
Popular Database Options
TYPE EXAMPLE SERVICES BEST FOR ────────────────────────────────────────────────────────── SQL PostgreSQL, MySQL Structured data, relations NoSQL MongoDB, DynamoDB Flexible data, documents Cloud SQL Supabase, PlanetScale SQL with built-in APIs SQLite libSQL (Turso) Simple apps, prototypes
Option 1: Using Prisma (Recommended for SQL)
Prisma is an ORM (Object-Relational Mapper). Instead of writing raw SQL, you write JavaScript. Prisma translates your JavaScript into SQL queries and handles the database connection for you.
Install Prisma
npm install prisma @prisma/client npx prisma init
Define Your Schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
createdAt DateTime @default(now())
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
}
Generate the Client and Run Migrations
npx prisma migrate dev --name init npx prisma generate
Create the Database Client File
// app/lib/db.js
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis;
const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
export default prisma;
This pattern prevents creating too many database connections during development when Next.js hot-reloads your files.
Diagram: Why Global Client Matters in Dev
WITHOUT GLOBAL PATTERN (development):
File saves → Next.js hot reloads → New PrismaClient created
File saves → Hot reload → Another new PrismaClient
File saves → Hot reload → Another new PrismaClient
Result: 100s of connections → Database overwhelmed
WITH GLOBAL PATTERN:
File saves → Next.js hot reloads → Checks globalThis.prisma
→ Already exists → Reuse it
Result: One connection → Database happy
Using Prisma in a Server Component
// app/blog/page.js
import prisma from '@/app/lib/db';
export default async function BlogPage() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
});
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Using Prisma in a Server Action
// app/actions.js
'use server';
import prisma from '@/app/lib/db';
import { revalidatePath } from 'next/cache';
export async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
await prisma.post.create({
data: { title, content, published: true }
});
revalidatePath('/blog');
}
Option 2: Using MongoDB with Mongoose
npm install mongoose
// app/lib/mongoose.js
import mongoose from 'mongoose';
let isConnected = false;
export async function connectDB() {
if (isConnected) return;
await mongoose.connect(process.env.MONGODB_URI);
isConnected = true;
}
// app/lib/models/Post.js
import mongoose from 'mongoose';
const PostSchema = new mongoose.Schema({
title: { type: String, required: true },
content: String,
createdAt: { type: Date, default: Date.now },
});
export const Post = mongoose.models.Post || mongoose.model('Post', PostSchema);
// Usage in a server component or action:
import { connectDB } from '@/app/lib/mongoose';
import { Post } from '@/app/lib/models/Post';
await connectDB();
const posts = await Post.find({}).sort({ createdAt: -1 });
Option 3: Supabase (PostgreSQL as a Service)
Supabase provides a hosted PostgreSQL database with a simple JavaScript client. It also includes built-in authentication and file storage.
npm install @supabase/supabase-js
// app/lib/supabase.js
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY // Use service key server-side only
);
// Fetch data from Supabase in a server component
const { data: posts, error } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false });
Diagram: The Full Data Flow
BROWSER ↓ (HTTP request) NEXT.JS SERVER ↓ (server component or action runs) DATABASE CLIENT (Prisma / Mongoose / Supabase) ↓ (query) DATABASE (PostgreSQL / MongoDB / Supabase) ↓ (data returned) SERVER renders HTML with data ↓ (HTML sent) BROWSER displays the page
Key Takeaway
Database code belongs on the server — in server components, server actions, and API routes. Use Prisma for SQL databases with type-safe queries and automatic migrations. Use Mongoose for MongoDB. Use Supabase for a managed PostgreSQL database with minimal setup. Always create a single, reusable database client instance to avoid connection overload, especially in development.
