Every piece of data in your MERN application starts as a document inserted into MongoDB. Whether a user registers an account, an author creates a blog post, or a reader leaves a comment — every creation operation flows from your Express controller through Mongoose down to a MongoDB insert. Understanding all the insert methods, their behaviours, and how to handle their errors gives you complete control over the creation layer of your MERN stack. In this lesson you will master insertOne, insertMany, ordered vs unordered inserts, and the Mongoose equivalents you will use every day.
Insert Methods Overview
| Method | What It Does | Returns |
|---|---|---|
db.collection.insertOne(doc) |
Insert a single document | { acknowledged, insertedId } |
db.collection.insertMany([docs]) |
Insert multiple documents | { acknowledged, insertedIds } |
Model.create(doc) |
Mongoose: insert one, runs validators and hooks | The saved document |
Model.create([docs]) |
Mongoose: insert many with validation | Array of saved documents |
new Model(doc).save() |
Mongoose: construct then save, runs all hooks | The saved document |
Model.insertMany([docs]) |
Mongoose: bulk insert, skips some middleware | { insertedDocs } |
Model.create() and new Model().save() both run Mongoose validators and pre/post save hooks. Model.insertMany() is faster for bulk inserts because it bypasses some middleware, but it also skips save hooks. For seeding a database with many records quickly, use insertMany(). For normal application inserts where validation and hooks matter, use create() or save().Model.create([array]) in Mongoose, it runs validation on each document and throws a ValidationError on the first failure. If you want to continue inserting valid documents even when some fail validation, use Model.insertMany(docs, { ordered: false }) — MongoDB will insert all valid documents and report errors for the invalid ones without stopping the bulk operation.insertOne — Single Document
// mongosh — raw driver
db.posts.insertOne({
title: "My First Blog Post",
slug: "my-first-blog-post",
body: "Content of the post...",
tags: ["mern", "beginner"],
published: false,
viewCount: 0,
createdAt: new Date(),
});
// Returns:
// { acknowledged: true, insertedId: ObjectId("64a1f2b3c8e4d5f6a7b8c9d0") }
// ── Mongoose equivalent ────────────────────────────────────────────────────────
const post = await Post.create({
title: "My First Blog Post",
body: "Content of the post...",
tags: ["mern", "beginner"],
author: req.user.id,
});
// Returns the full document including _id, timestamps, and any defaults applied
console.log(post._id); // ObjectId
console.log(post.createdAt); // Date — set by timestamps: true
console.log(post.published); // false — schema default applied
insertMany — Multiple Documents
// mongosh — insert multiple posts at once
db.posts.insertMany([
{ title: "Post One", body: "Content 1", published: true, createdAt: new Date() },
{ title: "Post Two", body: "Content 2", published: false, createdAt: new Date() },
{ title: "Post Three", body: "Content 3", published: true, createdAt: new Date() },
]);
// Returns:
// {
// acknowledged: true,
// insertedIds: {
// '0': ObjectId("64a1f2b3..."),
// '1': ObjectId("64a1f2b4..."),
// '2': ObjectId("64a1f2b5...")
// }
// }
// ── Mongoose equivalent — create() with array ──────────────────────────────────
const posts = await Post.create([
{ title: "Post One", body: "Content 1", published: true, author: adminId },
{ title: "Post Two", body: "Content 2", published: false, author: adminId },
]);
// Each document runs validation individually
// If document 1 fails validation — error thrown, document 2 is not inserted
new Model().save() — Construct Then Save
// Useful when you need to modify the document before saving
const post = new Post({
title: "Draft Post",
body: "Work in progress...",
author: req.user.id,
});
// You can modify the document before saving
post.slug = generateSlug(post.title); // custom slug generation
post.tags = extractTagsFromBody(post.body);
// Validate without saving first
await post.validate(); // throws ValidationError if invalid
// Then save
const saved = await post.save(); // runs pre('save') and post('save') hooks
console.log(saved._id); // available after save
Bulk Inserts for Seeding
// server/src/scripts/seedPosts.js
// Fast bulk insert using insertMany — skips save hooks for performance
const mongoose = require('mongoose');
const Post = require('../models/Post');
const seedPosts = async () => {
await mongoose.connect(process.env.MONGODB_URI);
// Clear existing posts first
await Post.deleteMany({});
console.log('Cleared existing posts');
const posts = Array.from({ length: 50 }, (_, i) => ({
title: `Sample Post ${i + 1}`,
body: `Content for post ${i + 1}. `.repeat(20),
slug: `sample-post-${i + 1}`,
tags: ['sample', i % 2 === 0 ? 'even' : 'odd'],
published: true,
author: new mongoose.Types.ObjectId(), // placeholder author ID
viewCount: Math.floor(Math.random() * 500),
createdAt: new Date(Date.now() - i * 24 * 60 * 60 * 1000), // spaced by 1 day
}));
// Model.insertMany — fast, skips save hooks, sets { ordered: true } by default
const result = await Post.insertMany(posts, { ordered: false });
console.log(`Seeded ${result.length} posts`);
await mongoose.connection.close();
};
seedPosts().catch(console.error);
Handling Duplicate Key Errors
// A unique index on 'slug' means inserting a duplicate slug throws an error
try {
await Post.create({ title: 'Existing Post', slug: 'existing-slug', ... });
} catch (err) {
if (err.code === 11000) {
// MongoServerError: E11000 duplicate key error collection: blogdb.posts
const field = Object.keys(err.keyValue)[0]; // 'slug'
console.error(`Duplicate value for field: ${field}`);
// In an Express controller: throw new AppError(`${field} already exists`, 409)
} else {
throw err; // re-throw unexpected errors
}
}
Common Mistakes
Mistake 1 — Not awaiting insert operations
❌ Wrong — missing await on an async insert:
const post = Post.create({ title: 'Post', body: '...' }); // missing await
console.log(post._id); // undefined — post is a Promise, not a document
✅ Correct:
const post = await Post.create({ title: 'Post', body: '...' }); // ✓
console.log(post._id); // ObjectId — document is saved and returned
Mistake 2 — Including the _id in insertMany when it may clash
❌ Wrong — supplying the same _id in a retry loop:
await Post.insertMany([{ _id: fixedId, title: '...' }]); // first call succeeds
await Post.insertMany([{ _id: fixedId, title: '...' }]); // duplicate key error
✅ Correct — omit _id and let MongoDB generate unique ObjectIds automatically.
Mistake 3 — Using insertMany with ordered: true and one bad document
❌ Wrong — one invalid document stops all subsequent inserts:
await Post.insertMany([
{ title: 'Good Post', body: '...', author: id }, // inserted
{ body: 'Missing title' }, // fails validation → stops here
{ title: 'Another Post', body: '...', author: id }, // never inserted
], { ordered: true }); // default — stops on first error
✅ Correct — use ordered: false to insert all valid documents and collect errors:
const result = await Post.insertMany(docs, { ordered: false });
// Inserts all valid documents, reports errors for invalid ones without stopping
Quick Reference
| Task | Mongoose Code |
|---|---|
| Create one document | await Model.create({ fields }) |
| Create many documents | await Model.create([{ doc1 }, { doc2 }]) |
| Construct and save | const doc = new Model({...}); await doc.save() |
| Fast bulk insert | await Model.insertMany(docs, { ordered: false }) |
| Check duplicate key | if (err.code === 11000) { ... } |
| Get inserted ID | const doc = await Model.create({...}); doc._id |