MongoDB in 2026: Still the Right Choice for Many Applications
MongoDB 8.0 delivers major query performance improvements (up to 30% faster reads on large collections) and Atlas Vector Search is now first-class — making it a natural fit for AI-augmented applications. Mongoose 8.x improves TypeScript support dramatically. Here's how to use both correctly.
Schema Design Principles
Embed vs Reference — The Golden Rule
- Embed when data is accessed together and the sub-document is bounded in size (comments under a post ≤ 100 items)
- Reference when data is large, grows unbounded, or is queried independently (users, products)
// ✅ Good — embed small, stable data
const PostSchema = new Schema({
title: String,
tags: [String], // embedded array, bounded
author: { type: ObjectId, ref: "User" }, // reference — User queried independently
});
// ❌ Bad — embedding unbounded array
const UserSchema = new Schema({
posts: [{ type: ObjectId, ref: "Post" }], // could grow to millions
});
Mongoose 8.x TypeScript Integration
import { Schema, model, InferSchemaType } from "mongoose";
const blogSchema = new Schema({
title: { type: String, required: true },
slug: { type: String, required: true, unique: true },
status: { type: String, enum: ["draft","published"], default: "draft" },
publishedAt: Date,
}, { timestamps: true });
type Blog = InferSchemaType<typeof blogSchema>;
// Blog is fully typed — no manual interface needed
export const Blog = model("Blog", blogSchema);
Indexing Strategy
// Compound index — matches query pattern exactly
blogSchema.index({ status: 1, publishedAt: -1 });
// Text search
blogSchema.index({ title: "text", content: "text" });
// TTL index — auto-delete expired documents
tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
Explain Your Queries
const explain = await Blog
.find({ status: "published" })
.sort({ publishedAt: -1 })
.explain("executionStats");
console.log(explain.executionStats.totalDocsExamined);
// Should equal totalDocsReturned — if not, you need an index
Connection Pooling in Serverless
// lib/db/connect.ts — singleton pattern for Next.js
let cached = global.mongoose;
export default async function dbConnect() {
if (cached?.conn) return cached.conn;
cached.promise = cached.promise || mongoose.connect(MONGODB_URI, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
});
cached.conn = await cached.promise;
return cached.conn;
}
Atlas Vector Search for AI Features
// Store embeddings alongside documents
const articleSchema = new Schema({
content: String,
embedding: [Number], // 1536-dim for text-embedding-3-small
});
// Vector search pipeline
const results = await Article.aggregate([{
$vectorSearch: {
index: "default",
queryVector: await embed(userQuery),
path: "embedding",
numCandidates: 100,
limit: 5,
}
}]);
Need a production-ready MongoDB architecture?
I design schemas and data layers that perform at scale. Let's discuss your project →