All articles

Building a Full-Stack App with Next.js 14 and MongoDB

T
The author
2 min read

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/api directory in App Router projects
  • MongoDB Atlas free tier is perfect for development and small production apps

Reactions

Comments

No comments yet.

Sign in to leave a comment.

T

Written by

The author