Introduction
PlanetScale is a serverless MySQL database powered by Vitess, designed to scale horizontally without downtime. Unlike traditional databases like MySQL or PostgreSQL, it offers a Git-inspired branching system, ultra-fast reads via Boost replicas, and perfect compatibility with ORMs like Prisma.
Why use it in 2026? Modern apps are exploding with traffic: PlanetScale handles billions of queries without manual sharding, with pixel-perfect billing. This intermediate tutorial shows you how to integrate PlanetScale into a Next.js app with Prisma—from creating the database to deploying with branching for features. By the end, you'll have a production-ready, infinitely scalable stack. Estimated time: 30 min.
Prerequisites
- Free account on PlanetScale (sign up in 2 min)
- Node.js 20+ and npm/yarn/pnpm
- Basic knowledge of Next.js 15 and Prisma 6
- Vercel CLI for optional deployment
- Tool like pscale installed via
npm i -g @planetscale/cli
Initialize the Next.js Project
npx create-next-app@latest mon-app-planetscale --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd mon-app-planetscale
npm install @prisma/client prisma @planetscale/database
npm install -D @types/nodeThis command creates a complete Next.js 15 App Router project with TypeScript. We install Prisma for the ORM, the PlanetScale driver for direct connections, and Node types. Skip empty templates to save time on boilerplate structure.
Create the PlanetScale Database
Log in to your PlanetScale dashboard:
- Click New database, name it
mon-app-db, regioneu-westfor low latency. - Enable Branching (on by default).
- In Connect, generate a Dev URL (format
mysql://user:pass@aws.connect.psdb.cloud/db?sslaccept=strict). - Copy the Password (non-reusable).
Analogy: Like Git, create a
main branch for production and dev for testing without risking live data.Configure the Prisma Schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
// PlanetScale specifics
directUrl = env("DIRECT_URL") // For push migrations
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// Indexes for PlanetScale performance
index on User(email)
index on Post(published, createdAt)This schema defines two models with relations and indexes optimized for Vitess (no strict FKs by default, but Prisma handles relations). Use DIRECT_URL for migrations on PlanetScale. Pitfall: Skip indexes, and queries slow down on large volumes.
Run the First Migration
echo "DATABASE_URL=\"mysql://username:password@host:3306/mon-app-db?sslaccept=strict&ssl={'rejectUnauthorized':true}\" > .env
npx prisma generate
npx prisma db push --accept-data-loss
npx prisma generateSet DATABASE_URL from your PlanetScale dashboard (replace creds). db push applies the schema without downtime on PlanetScale. Use --accept-data-loss for dev; in prod, use pscale branch create. Regenerate the Prisma client afterward.
Test Queries with Prisma Client
Create an API route to validate the integration. PlanetScale excels at reads: use implicit connection pooling via Prisma Accelerate (optional, free up to 5GB).
Implement a CRUD API Route
import { PrismaClient } from '@prisma/client';
import { NextRequest, NextResponse } from 'next/server';
const prisma = new PrismaClient();
export async function GET() {
try {
const posts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
orderBy: { createdAt: 'desc' },
});
return NextResponse.json(posts);
} catch (error) {
return NextResponse.json({ error: 'Erreur fetch posts' }, { status: 500 });
}
}
export async function POST(request: NextRequest) {
try {
const { title, content, authorEmail } = await request.json();
const author = await prisma.user.upsert({
where: { email: authorEmail },
update: {},
create: { email: authorEmail, name: authorEmail },
});
const post = await prisma.post.create({
data: {
title,
content,
authorId: author.id,
},
});
return NextResponse.json(post, { status: 201 });
} catch (error) {
return NextResponse.json({ error: 'Erreur création post' }, { status: 500 });
}
}
// Cleanup Prisma
process.on('beforeExit', async () => {
await prisma.$disconnect();
});This route handles GET (optimized reads) and POST (creation with upsert for users). Use include for efficient joins on PlanetScale. Add cleanup to avoid memory leaks in serverless; test with curl -X POST http://localhost:3000/api/posts -d '{"title":"Test","content":"Contenu","authorEmail":"test@example.com"}'.
Use the CLI for Branching
pscale auth login
pscale database list
pscale branch create dev mon-app-db --org your-org
pscale branch list mon-app-db
pscale database branchpoint mon-app-db main dev
pscale repl connect mon-app-db devAuthenticate, list DBs, create a dev branch from main. branchpoint marks the starting point for safe merges. repl connect for local MySQL shell. Pitfall: Always merge via UI for audits; branches expire after 7 days of inactivity.
Deploy to Vercel with Secrets
Steps:
vercel env add DATABASE_URL production(paste the Prod URL from PlanetScale).vercel --prod.
PlanetScale + Vercel = perfect combo: fully serverless, auto-scaling.
Direct Queries with @planetscale/database
import { connect } from '@planetscale/database';
import { NextResponse } from 'next/server';
const config = {
url: process.env.DATABASE_URL!,
};
const db = connect(config);
export async function GET() {
const [rows] = await db.execute('SELECT * FROM Post WHERE published = true ORDER BY createdAt DESC LIMIT 10');
return NextResponse.json(rows);
}For ultra-performant raw queries (no ORM overhead), use the official driver. Ideal for analytics or Vitess features like sharding. execute returns rows; always parameterize for SQL injection (boolean is safe here).
Best Practices
- Strategic indexes: Always index common
WHEREclauses (e.g.,published,userId) for Vitess. - Connection pooling: Enable Prisma Accelerate for >100 req/s without timeouts.
- Branching workflow:
dev→ tests →pr→ mergemainfor safe CI/CD. - Monitoring: Use PlanetScale Insights for query performance; keep selects under 10ms.
- Strict SSL: Keep
sslaccept=strictin production for security.
Common Errors to Avoid
- No DIRECT_URL: Migrations fail without it (PlanetScale blocks DDL on prod URL).
- Manual foreign keys: Vitess handles them poorly; rely on Prisma soft relations.
- Forget Prisma cleanup: Memory leaks in serverless → crashes on Vercel.
- Ignore regions: Pick
eu-westfor EU apps; >200ms latency kills UX.
Next Steps
- Official docs: PlanetScale + Prisma
- Advanced: Boost for read replicas, Vitess sharding.
- Learni Group trainings on serverless databases and advanced Next.js.
- Example repo: Fork this tutorial on GitHub to experiment.