MongoDB CRUD with Mongoose
Schema validation, virtuals, populate, indexes, and aggregation
What You’ll Build
After following this guide, you will have a working implementation of mongodb crud with mongoose in your project. Build a complete MongoDB data layer with Mongoose. Covers schema design with validation, virtual fields, populating references (joins), indexing for performance, and aggregation pipelines for analytics queries.
Use Cases & Problems Solved
- Set up a production-ready database layer with type-safe queries
- Manage schema changes through migrations without data loss
- Avoid raw SQL injection risks and inconsistent data access patterns
Prerequisites
- MongoDB running locally or MongoDB Atlas connection string
- Node.js 18+
Step-by-Step Implementation
Install and connect
The following snippet shows how to install and connect. Copy this into your project and adjust the values for your environment.
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp')
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('Connection error:', err));
Define schema with validation
The following snippet shows how to define schema with validation. Copy this into your project and adjust the values for your environment.
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true, lowercase: true,
match: [/^\S+@\S+\.\S+$/, 'Invalid email format'] },
name: { type: String, required: true, minlength: 2 },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }],
}, { timestamps: true });
// Virtual field
userSchema.virtual('postCount').get(function() {
return this.posts.length;
});
const User = mongoose.model('User', userSchema);
CRUD with populate
The following snippet shows how to crud with populate. Copy this into your project and adjust the values for your environment.
// Create
const user = await User.create({ email: 'bob@test.com', name: 'Bob' });
// Read with populated references
const userWithPosts = await User.findById(userId).populate('posts');
// Update
await User.findByIdAndUpdate(userId, { name: 'Robert' }, { new: true, runValidators: true });
// Delete
await User.findByIdAndDelete(userId);
// Aggregation
const stats = await User.aggregate([
{ $group: { _id: '$role', count: { $sum: 1 } } },
]);
⚠️ Don’t Do This
❌ Forgetting to add indexes on fields you query frequently
// Slow! Full collection scan on every query
const user = await User.findOne({ email: 'bob@test.com' });
✅ Add indexes for frequently queried fields
const userSchema = new mongoose.Schema({
email: { type: String, unique: true, index: true }, // indexed!
name: String,
});
// Or: userSchema.index({ email: 1, createdAt: -1 }); // compound index
Testing
Verify your implementation with these tests:
// __tests__/mongodb-crud-with-mongoose.test.ts
import { describe, it, expect } from 'vitest';
describe('MongoDB CRUD with Mongoose', () => {
it('should initialize without errors', () => {
// Test that the setup completes successfully
expect(() => setup()).not.toThrow();
});
it('should handle the primary use case', async () => {
const result = await execute();
expect(result).toBeDefined();
expect(result.success).toBe(true);
});
it('should handle edge cases', async () => {
// Test with empty/null input
const result = await execute(null);
expect(result.error).toBeDefined();
});
});
Verification
node app.js
# Check MongoDB for documents:
mongosh
> use myapp
> db.users.find().pretty() Related Specs
SQLite for Desktop/Local Apps
better-sqlite3 setup, WAL mode, concurrent access, and performance tuning
Redis Caching for API Responses
Cache-aside pattern, TTL, invalidation, and cache stampede prevention
Safe Database Migrations
Zero-downtime migration patterns, rollback strategies, and data backfills