Next.js 14 with the App Router is a fantastic choice for full-stack JavaScript applications. Combined with MongoDB, you get a flexible, schema-optional database that pairs naturally with JavaScript's object model.
In this guide, I'll walk through building a complete CRUD API using Next.js 14 Route Handlers and Mongoose.
Project Setup
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm install mongoose
Connecting to MongoDB
Create a reusable database connection utility. This is critical — you don't want to create a new connection on every request.
// src/lib/db.ts
import mongoose from 'mongoose'
const MONGODB_URI = process.env.MONGODB_URI!
if (!MONGODB_URI) {
throw new Error('MONGODB_URI environment variable not defined')
}
let cached = (global as any).mongoose ?? { conn: null, promise: null }
export async function connectDB() {
if (cached.conn) return cached.conn
if (!cached.promise) {
cached.promise = mongoose.connect(MONGODB_URI)
}
cached.conn = await cached.promise
return cached.conn
}
Defining a Model
// src/models/Post.ts
import mongoose, { Schema, model, models } from 'mongoose'
const PostSchema = new Schema({
title: { type: String, required: true, trim: true },
content: { type: String, required: true },
author: { type: String, required: true },
tags: [{ type: String }],
published: { type: Boolean, default: false },
}, { timestamps: true })
export const Post = models.Post || model('Post', PostSchema)
Route Handlers
Next.js 14 uses Route Handlers instead of API Routes. Here's a complete CRUD implementation:
// src/app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { connectDB } from '@/lib/db'
import { Post } from '@/models/Post'
export async function GET() {
await connectDB()
const posts = await Post.find({ published: true }).sort({ createdAt: -1 })
return NextResponse.json(posts)
}
export async function POST(req: NextRequest) {
await connectDB()
const body = await req.json()
try {
const post = await Post.create(body)
return NextResponse.json(post, { status: 201 })
} catch (error) {
return NextResponse.json({ error: 'Failed to create post' }, { status: 400 })
}
}
Environment Variables
Create .env.local in your project root:
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/mydb
For production on Vercel, add this in your project's Environment Variables settings.
Deploying to Vercel
git push origin main
# Vercel auto-deploys on push if connected
That's it. Vercel handles the serverless functions automatically — no server configuration needed.
Key Takeaways
- Always cache your Mongoose connection to avoid exhausting connection pools
- Use
models.Post || model(...)to prevent model re-registration in hot reload - Route Handlers replace the old
pages/apidirectory in App Router projects - MongoDB Atlas free tier is perfect for development and small production apps