Delete Documents
MongoDB provides deleteOne() and deleteMany() for removing documents. Always use a precise filter — MongoDB will happily delete more than you intend.
deleteOne()
deleteOne() removes the first document that matches the filter. If multiple documents match, only one is deleted.
deleteOne() — remove a single document
const { MongoClient, ObjectId } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('mydb');
const users = db.collection('users');
// Delete by _id (most precise)
await users.deleteOne({
_id: new ObjectId('64a1f2c3d4e5f6a7b8c9d0e1'),
});
// Delete by a unique field
await users.deleteOne({ email: 'test@example.com' });
// Delete the oldest inactive session
await db.collection('sessions').deleteOne(
{ active: false },
// Without a sort, which document is deleted is non-deterministic
);deleteOne() result
{ acknowledged: true, deletedCount: 1 }
// If no document matched the filter:
{ acknowledged: true, deletedCount: 0 }deleteMany()
deleteMany() removes all documents matching the filter. It returns the count of deleted documents.
deleteMany() — remove multiple documents
// Delete all inactive users
const result = await users.deleteMany({ active: false });
console.log(`Deleted ${result.deletedCount} inactive users`);
// Delete records older than 90 days
const cutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
await db.collection('auditLogs').deleteMany({
createdAt: { $lt: cutoff },
});
// Delete all documents with a specific tag
await db.collection('articles').deleteMany({
tags: 'draft',
});findOneAndDelete()
findOneAndDelete() atomically finds and removes a document, returning the deleted document. This is useful when you need the document's content after deletion — for example, implementing a work queue where you pop and process a job in one step.
findOneAndDelete() — return deleted document
// Pop the highest-priority pending job from a queue
const job = await db.collection('jobs').findOneAndDelete(
{ status: 'pending' },
{ sort: { priority: -1, createdAt: 1 } }
);
if (job) {
console.log('Processing job:', job._id, job.type);
await processJob(job);
} else {
console.log('Queue is empty');
}
// Delete and inspect a one-time verification token
const token = await db.collection('tokens').findOneAndDelete({
value: req.params.token,
expiresAt: { $gt: new Date() },
});
if (!token) {
throw new Error('Invalid or expired token');
}Soft Delete Pattern
In production, hard deletes are often replaced with soft deletes — adding a deletedAt timestamp field instead of removing the document. This preserves audit history, supports undo operations, and prevents accidental data loss.
Soft delete implementation
// Soft-delete a user (mark as deleted instead of removing)
await users.updateOne(
{ _id: userId },
{
$set: {
deletedAt: new Date(),
deletedBy: currentUser._id,
},
}
);
// Query only active (non-deleted) users
const activeUsers = await users
.find({ deletedAt: { $exists: false } })
.toArray();
// Restore a soft-deleted user
await users.updateOne(
{ _id: userId },
{ $unset: { deletedAt: '', deletedBy: '' } }
);
// Create a partial index to keep queries fast
await users.createIndex(
{ email: 1 },
{
unique: true,
partialFilterExpression: { deletedAt: { $exists: false } },
}
);Dropping Collections and Databases
When you need to remove an entire collection or database, use drop() or dropDatabase(). These operations are instant and bypass document-level operations entirely.
Drop operations
// Drop an entire collection (removes all documents AND indexes)
await db.collection('tempImports').drop();
// Drop the entire database
await db.dropDatabase();
// Safe pattern: check existence before dropping
const collections = await db.listCollections({ name: 'tempImports' }).toArray();
if (collections.length > 0) {
await db.collection('tempImports').drop();
}TTL Indexes for Automatic Deletion
For time-based data such as sessions, logs, and caches, use a TTL (Time-To-Live) index to auto-expire documents. MongoDB's background thread checks TTL indexes every 60 seconds and removes expired documents automatically — no application-level cleanup needed.
TTL index — auto-delete after 1 hour
// Create a TTL index on the 'createdAt' field
// Documents are deleted 3600 seconds (1 hour) after createdAt
await db.collection('sessions').createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 }
);
// Insert a session — it will auto-expire in 1 hour
await db.collection('sessions').insertOne({
userId: 'user123',
token: 'abc...',
createdAt: new Date(), // TTL is calculated from this field
});
// TTL index for log retention (delete after 30 days)
await db.collection('requestLogs').createIndex(
{ timestamp: 1 },
{ expireAfterSeconds: 30 * 24 * 60 * 60 }
);Bulk Delete with bulkWrite()
bulkWrite() lets you combine multiple delete (and other write) operations into a single round-trip to the server. This is more efficient than issuing many individual operations.
Bulk delete operations
const result = await users.bulkWrite([
// Delete a specific user by ID
{
deleteOne: {
filter: { _id: new ObjectId('64a1f2c3d4e5f6a7b8c9d0e1') },
},
},
// Delete all unverified users older than 7 days
{
deleteMany: {
filter: {
verified: false,
createdAt: {
$lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
},
},
},
},
]);
console.log('Deleted:', result.deletedCount);Method | Returns | Use Case |
|---|---|---|
deleteOne() | { deletedCount } | Remove a single known document |
deleteMany() | { deletedCount } | Bulk removal by condition |
findOneAndDelete() | The deleted document | Pop-and-process patterns, queue consumers |
drop() (collection) | void | Wipe an entire collection including indexes |
dropDatabase() | void | Remove an entire database and all its collections |