MongoDB Atlas with Mongoose
Based on the rackit codebase.
Prerequisites
- Node.js 18+
- A running MongoDB instance — one of:
- MongoDB Atlas (cloud, recommended)
- Local install via MongoDB Community Server
- Docker:
docker run -d -p 27017:27017 mongo
- A Next.js 13+ project using the App Router (
src/app/) - TypeScript (recommended)
Step 1 — Install dependencies
Two packages are needed:
mongodb— the official MongoDB driver (used by NextAuth adapter and for direct client access)mongoose— ODM for schema-based model definitions
1 | npm install mongodb mongoose |
If using NextAuth.js with a MongoDB adapter:
1 | npm install next-auth @auth/mongodb-adapter |
Step 2 — Set the environment variable
Add to .env.local:
1 | MONGODB_URI=mongodb+srv://<user>:<password>@cluster0.xxxxx.mongodb.net/<dbname>?retryWrites=true&w=majority |
For local MongoDB:
1 | MONGODB_URI=mongodb://localhost:27017/myapp |
Never commit
.env.localto source control. Add it to.gitignore.
Step 3 — Create the MongoClient helper (src/lib/mongodb.ts)
This file creates a singleton MongoClient promise, reusing the connection across hot reloads in development and across requests in production.
1 | import { MongoClient } from "mongodb"; |
Used by: NextAuth’s
MongoDBAdapter.
Step 4 — Create the Mongoose connection helper (src/lib/db.ts)
Lazy initialisation — only connects if not already connected. Safe to call from any server action or API route.
1 | import mongoose from "mongoose"; |
Used by: Mongoose models and server actions.
Step 5 — Define Mongoose models (src/models/)
Each model follows this pattern to avoid re-registering on hot reload:
1 | import mongoose, { Schema } from "mongoose"; |
Key conventions:
- Always guard with
mongoose.models.ModelName || mongoose.model(...)to prevent “Cannot overwrite model” errors on hot reload. - Place all models under
src/models/. - Use
timestampsoption for automaticcreatedAt/updatedAtfields.
Step 6 — Use the DB in Server Actions or Route Handlers
1 | import { connectDB } from "@/lib/db"; |
Always call connectDB() before any Mongoose query.
Step 7 — (Optional) Wire up NextAuth MongoDB adapter
If using NextAuth.js for authentication, pass the clientPromise from Step 3 to the adapter. This stores sessions, accounts, and verification tokens in MongoDB automatically.
1 | import NextAuth from "next-auth"; |
Summary of files created
| File | Purpose |
|---|---|
src/lib/mongodb.ts |
Singleton MongoClient promise for the raw driver / NextAuth adapter |
src/lib/db.ts |
Lazy Mongoose connection helper called before every query |
src/models/*.ts |
Mongoose schema + model definitions (one file per collection) |
.env.local |
MONGODB_URI connection string (not committed) |
Common pitfalls
| Problem | Fix |
|---|---|
| “Cannot overwrite model once compiled” | Guard all models with mongoose.models.X || mongoose.model(...) |
| Connection spam in development | Use global._mongoClientPromise to survive hot reloads |
MONGODB_URI undefined at runtime |
Check .env.local exists and Next.js was restarted after adding it |
| Mongoose not connected before query | Always await connectDB() at the top of every server action |
| Storing secrets in source control | Keep .env.local in .gitignore; use Vercel env vars in production |