Choosing the right database is one of the key decisions when building a Next.js application. In this module, we will explore the most popular database options along with their advantages and disadvantages in the context of Next.js applications.
Databases can be divided into two main categories: SQL (relational) and NoSQL (non-relational). Each has its own characteristic features that determine which projects they are best suited for.
Relational databases organize data in tables with a predefined structure and relationships between them. Examples of popular SQL databases include:
PostgreSQL
MySQL/MariaDB
SQLite
NoSQL databases offer greater flexibility in data structure and typically better horizontal scalability. The main types are:
Dokumentowe (MongoDB, Firebase Firestore)
Key-Value (Redis, DynamoDB)
Wide-Column (Cassandra, HBase)
Grafowe (Neo4j, Amazon Neptune)
PostgreSQL combined with the Prisma ORM is one of the most popular options for Next.js applications:
1// Example Prisma model for PostgreSQL
2model User {
3 id Int @id @default(autoincrement())
4 email String @unique
5 name String?
6 posts Post[]
7 profile Profile?
8 createdAt DateTime @default(now())
9 updatedAt DateTime @updatedAt
10}
11
12model Post {
13 id Int @id @default(autoincrement())
14 title String
15 content String?
16 published Boolean @default(false)
17 author User @relation(fields: [authorId], references: [id])
18 authorId Int
19 createdAt DateTime @default(now())
20 updatedAt DateTime @updatedAt
21}
22
23model Profile {
24 id Int @id @default(autoincrement())
25 bio String
26 user User @relation(fields: [userId], references: [id])
27 userId Int @unique
28}Advantages:
Disadvantages:
MongoDB is a popular non-relational document database, often used with Next.js:
1// Example Mongoose model for MongoDB
2import mongoose from 'mongoose';
3
4const UserSchema = new mongoose.Schema({
5 name: {
6 type: String,
7 required: [true, 'Please provide a name'],
8 maxlength: [50, 'Name cannot be longer than 50 characters']
9 },
10 email: {
11 type: String,
12 required: [true, 'Please provide an email'],
13 unique: true,
14 match: [
15 /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
16 'Please provide a valid email address'
17 ]
18 },
19 posts: [
20 {
21 type: mongoose.Schema.Types.ObjectId,
22 ref: 'Post'
23 }
24 ],
25 createdAt: {
26 type: Date,
27 default: Date.now
28 }
29});
30
31export default mongoose.models.User || mongoose.model('User', UserSchema);Zalety:
Wady:
Serverless databases are a new trend that perfectly fits the Next.js architecture:
1// Example of using PlanetScale with Next.js
2import { PrismaClient } from '@prisma/client';
3
4const prisma = new PrismaClient();
5
6// In API route or Server Component
7export async function getUsers() {
8 try {
9 const users = await prisma.user.findMany({
10 include: { posts: true }
11 });
12 return users;
13 } catch (error) {
14 console.error('Database error:', error);
15 throw new Error('Failed to fetch users');
16 }
17}Popular options:
Zalety:
Wady:
A novelty in the Next.js ecosystem are databases running on the edge:
1// Example of using Cloudflare D1 with Next.js
2// In API Route (Pages Router) or Route Handler (App Router)
3import { D1Database } from '@cloudflare/workers-types';
4
5export const runtime = 'edge';
6
7export async function GET() {
8 const db = process.env.DB as unknown as D1Database;
9
10 const { results } = await db
11 .prepare('SELECT * FROM users WHERE status = ?')
12 .bind('active')
13 .all();
14
15 return Response.json({ users: results });
16}Zalety:
Wady:
1// lib/prisma.ts
2import { PrismaClient } from '@prisma/client';
3
4let prisma: PrismaClient;
5
6if (process.env.NODE_ENV === 'production') {
7 prisma = new PrismaClient();
8} else {
9 // Preventing multiple instances in dev mode
10 if (!global.prisma) {
11 global.prisma = new PrismaClient();
12 }
13 prisma = global.prisma;
14}
15
16export default prisma;
17
18// In Server Component (App Router)
19// app/users/page.tsx
20import prisma from '@/lib/prisma';
21
22export default async function UsersPage() {
23 const users = await prisma.user.findMany({
24 include: {
25 posts: true,
26 },
27 });
28
29 return (
30 <div>
31 <h1>Users</h1>
32 <ul>
33 {users.map((user) => (
34 <li key={user.id}>
35 {user.name} - {user.posts.length} posts
36 </li>
37 ))}
38 </ul>
39 </div>
40 );
41}1// lib/mongodb.js
2import { MongoClient } from 'mongodb';
3
4const MONGODB_URI = process.env.MONGODB_URI;
5const MONGODB_DB = process.env.MONGODB_DB;
6
7if (!MONGODB_URI) {
8 throw new Error('Define MONGODB_URI in .env.local file');
9}
10
11if (!MONGODB_DB) {
12 throw new Error('Define MONGODB_DB in .env.local file');
13}
14
15let cachedClient = null;
16let cachedDb = null;
17
18export async function connectToDatabase() {
19 if (cachedClient && cachedDb) {
20 return { client: cachedClient, db: cachedDb };
21 }
22
23 const client = await MongoClient.connect(MONGODB_URI, {
24 useNewUrlParser: true,
25 useUnifiedTopology: true,
26 });
27
28 const db = await client.db(MONGODB_DB);
29
30 cachedClient = client;
31 cachedDb = db;
32
33 return { client, db };
34}
35
36// In API Route
37// pages/api/users.js
38import { connectToDatabase } from '@/lib/mongodb';
39
40export default async function handler(req, res) {
41 const { db } = await connectToDatabase();
42
43 const users = await db
44 .collection('users')
45 .find({})
46 .limit(20)
47 .toArray();
48
49 res.status(200).json(users);
50}When choosing a database for Next.js, it is worth following these guidelines:
Regardless of the choice, Next.js offers excellent integration mechanisms with all popular databases. In the next module, we will learn how to configure an ORM for the chosen solution to further streamline working with the database.