Chapter 51: Database
1. Current realistic database landscape for Node.js (early 2026)
| Database | Approx. popularity (Node.js backends) | Best TypeScript experience | Best library/stack right now (2025–2026) | Typical project size / team | Performance rank | Learning curve | Ecosystem momentum |
|---|---|---|---|---|---|---|---|
| PostgreSQL | ~45–55% | Excellent | Prisma / Drizzle / Kysely | Medium → very large | Very high | Medium | Extremely strong |
| MongoDB | ~30–40% | Very good | Mongoose / MongoDB native driver | Small → large | High | Low | Still very strong |
| MySQL / MariaDB | ~8–15% | Good | Prisma / Drizzle / mysql2 | Small → medium | High | Low–medium | Stable |
| SQLite | ~5–10% (growing fast) | Excellent | Better-SQLite3 / Prisma / LibSQL | Small → medium | Very high (local) | Low | Growing fast |
| PlanetScale / Neon (serverless Postgres) | ~5–12% (fast rising) | Excellent | Prisma / Drizzle | Small → large | Very high | Low–medium | Very strong |
| Supabase (Postgres + auth) | ~5–10% (very fast growing) | Excellent | Supabase JS client + Prisma (optional) | Small → medium | High | Low | Explosive growth |
Quick verdict right now (early 2026)
- PostgreSQL + Prisma → the most common serious choice
- PostgreSQL + Drizzle → the fastest growing choice (type-safe SQL)
- MongoDB + Mongoose → still very strong for rapid prototyping & flexible schemas
- SQLite → exploding for local-first / edge / small apps
- Supabase → very popular for indie hackers & startups (Postgres + auth + storage in one)
We will build two complete examples:
- PostgreSQL + Prisma (most widely used serious stack)
- MongoDB + Mongoose (still very common & beginner-friendly)
Example 1 – PostgreSQL + Prisma (most recommended serious stack)
1.1 Project setup
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
mkdir task-api-prisma cd task-api-prisma npm init -y npm pkg set type=module # Core dependencies npm install express cors helmet dotenv zod jsonwebtoken bcryptjs @prisma/client # Development dependencies npm install -D \ typescript @types/node @types/express @types/cors @types/jsonwebtoken @types/bcryptjs \ prisma tsx nodemon eslint prettier eslint-config-standard-with-typescript \ @typescript-eslint/parser @typescript-eslint/eslint-plugin |
Initialize Prisma
|
0 1 2 3 4 5 6 |
npx prisma init |
Edit .env
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/taskdb?schema=public"prisma/schema.prisma
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) email String @unique password String name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tasks Task[] } model Task { id String @id @default(uuid()) title String description String? completed Boolean @default(false) dueDate DateTime? priority String @default("medium") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) } |
Create & migrate database
|
0 1 2 3 4 5 6 |
npx prisma migrate dev --name init |
Generate Prisma Client
|
0 1 2 3 4 5 6 |
npx prisma generate |
1.2 Realistic Express server with Prisma
src/index.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import express from 'express' import cors from 'cors' import helmet from 'helmet' import 'dotenv/config' import { prisma } from './prisma/client.js' import authRoutes from './routes/auth.routes.js' import taskRoutes from './routes/task.routes.js' const app = express() app.use(helmet()) app.use(cors()) app.use(express.json()) // Health check + database connection check app.get('/health', async (req, res) => { try { await prisma.$queryRaw`SELECT 1` res.json({ status: 'ok', database: 'connected', uptime: process.uptime(), timestamp: new Date().toISOString() }) } catch (err) { res.status(500).json({ status: 'error', database: 'disconnected' }) } }) app.use('/api/auth', authRoutes) app.use('/api/tasks', taskRoutes) const PORT = Number(process.env.PORT) || 5000 app.listen(PORT, async () => { console.log(`Server running → http://localhost:${PORT}`) console.log('Prisma connected') }) |
src/prisma/client.ts (singleton pattern – very common)
|
0 1 2 3 4 5 6 7 8 9 10 |
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() export { prisma } |
src/routes/auth.routes.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import { Router } from 'express' import { z } from 'zod' import bcrypt from 'bcryptjs' import jwt from 'jsonwebtoken' import { prisma } from '../prisma/client.js' import env from '../config/env.js' const router = Router() const registerSchema = z.object({ email: z.string().email(), password: z.string().min(8), name: z.string().min(2).optional() }) const loginSchema = z.object({ email: z.string().email(), password: z.string().min(8) }) router.post('/register', async (req, res) => { const { email, password, name } = registerSchema.parse(req.body) const existing = await prisma.user.findUnique({ where: { email } }) if (existing) return res.status(409).json({ error: 'Email already exists' }) const passwordHash = await bcrypt.hash(password, 12) const user = await prisma.user.create({ data: { email, passwordHash, name }, select: { id: true, email: true, name: true } }) const token = jwt.sign({ userId: user.id }, env.JWT_SECRET, { expiresIn: '7d' }) res.status(201).json({ user, token }) }) router.post('/login', async (req, res) => { const { email, password } = loginSchema.parse(req.body) const user = await prisma.user.findUnique({ where: { email } }) if (!user) return res.status(401).json({ error: 'Invalid credentials' }) const valid = await bcrypt.compare(password, user.passwordHash) if (!valid) return res.status(401).json({ error: 'Invalid credentials' }) const token = jwt.sign({ userId: user.id }, env.JWT_SECRET, { expiresIn: '7d' }) res.json({ user: { id: user.id, email: user.email, name: user.name }, token }) }) export default router |
2. MongoDB + Mongoose version (alternative stack)
Install Mongoose
|
0 1 2 3 4 5 6 7 |
npm install mongoose npm install -D @types/mongoose |
src/models/user.model.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import mongoose from 'mongoose' import bcrypt from 'bcryptjs' const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true }, name: String, createdAt: { type: Date, default: Date.now } }) userSchema.pre('save', async function (next) { if (!this.isModified('password')) return next() this.password = await bcrypt.hash(this.password, 12) next() }) userSchema.methods.comparePassword = async function (candidatePassword: string) { return bcrypt.compare(candidatePassword, this.password) } export const User = mongoose.model('User', userSchema) |
Login route example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
router.post('/login', async (req, res) => { const { email, password } = loginSchema.parse(req.body) const user = await User.findOne({ email }) if (!user) return res.status(401).json({ error: 'Invalid credentials' }) const valid = await user.comparePassword(password) if (!valid) return res.status(401).json({ error: 'Invalid credentials' }) const token = jwt.sign({ userId: user._id }, env.JWT_SECRET, { expiresIn: '7d' }) res.json({ user: { id: user._id, email: user.email, name: user.name }, token }) }) |
Summary – PostgreSQL vs MongoDB in Node.js 2025–2026
| Aspect | PostgreSQL + Prisma / Drizzle | MongoDB + Mongoose |
|---|---|---|
| Schema strictness | Strict (very good for most apps) | Flexible (good for evolving schemas) |
| TypeScript experience | Excellent (especially Drizzle) | Very good (Mongoose + typegoose) |
| Performance (complex queries) | Excellent | Good |
| Performance (simple CRUD) | Very good | Excellent |
| Learning curve | Medium | Low |
| Ecosystem (tools, hosting) | Extremely strong | Still very strong |
| Transaction support | Full ACID | Multi-document transactions (since 4.0) |
| Typical use-case | Most serious production APIs | Rapid prototyping, flexible data models |
My personal recommendation right now (early 2026)
- New serious project → PostgreSQL + Prisma or PostgreSQL + Drizzle
- Rapid prototype / flexible schema → MongoDB + Mongoose
- Edge / serverless → Neon / Supabase (Postgres) + Prisma or PlanetScale
- Local-first / offline → SQLite + Prisma / Better-SQLite3
Which database direction would you like to go much deeper into next?
- Prisma full example (auth + tasks + relations + pagination)
- Drizzle ORM complete setup (type-safe SQL)
- Mongoose with advanced schema patterns (discriminators, middleware, virtuals)
- Supabase auth + PostgreSQL in Node.js
- Connection pooling, transaction management, error handling patterns
Just tell me what you want to build or understand next — I’ll continue with complete, production-ready code and explanations. 😊
