Deleting Documents
Learn how to delete documents and implement soft delete patterns
Deleting documents requires careful consideration. Use hard deletes for performance, soft deletes for recovery.
Navigation:
- Hard Delete - Permanent removal
- Soft Delete - Recoverable deletion
- Bulk Operations - Delete multiple documents efficiently
Hard Delete
The basic delete operation removes a document permanently:
await db.users.delete({
where: {
id: 'user_123',
email: 'john@example.com' // Partition key required
}
});Important: Deletion is permanent and cannot be undone. Always ensure you have the partition key in the where clause—this is enforced at compile time.
Cost: ~5 RU
Soft Delete
Instead of permanently deleting documents, mark them as deleted:
// Instead of deleting, mark as inactive
await db.users.update({
where: { id: 'user_123', email: 'john@example.com' },
data: {
isActive: false,
deletedAt: new Date()
}
});
// Query only active users
const activeUsers = await db.users.findMany({
partitionKey: 'tenant_123',
where: {
isActive: true
}
});Why Soft Delete?
- Data Recovery: Can restore deleted data
- Audit Trail: Keep history of what was deleted and when
- Referential Integrity: Other documents can still reference deleted items
- Analytics: Analyze deleted data patterns
Implementing Soft Delete
Complete implementation:
// Define schema with soft delete fields
const users = container('users', {
id: field.string(),
email: field.string(),
name: field.string(),
isDeleted: field.boolean().default(false),
deletedAt: field.date().optional(),
deletedBy: field.string().optional()
}).partitionKey('email');
// Helper functions
async function softDeleteUser(id: string, email: string, deletedBy: string) {
return await db.users.update({
where: { id, email },
data: {
isDeleted: true,
deletedAt: new Date(),
deletedBy
}
});
}
async function restoreUser(id: string, email: string) {
return await db.users.update({
where: { id, email },
data: {
isDeleted: false,
deletedAt: null,
deletedBy: null
}
});
}
// Query excluding soft-deleted items
async function findAllUsers(partitionKey: string) {
return await db.users.findMany({
partitionKey,
where: {
isDeleted: false
}
});
}Cleanup Old Soft Deletes
Automatically clean up old soft-deleted items:
async function cleanupOldSoftDeletes() {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const deletedUsers = await db.users.findMany({
enableCrossPartitionQuery: true,
where: {
isDeleted: true,
deletedAt: { lt: thirtyDaysAgo }
}
});
// Permanently delete old soft-deleted items
for (const user of deletedUsers) {
await db.users.delete({
where: { id: user.id, email: user.email }
});
}
}Cascade Delete Pattern
When deleting a parent document, you may need to delete related documents:
async function deleteUserWithRelatedData(userId: string, email: string) {
// Find all related posts
const posts = await db.posts.findMany({
partitionKey: userId
});
// Delete all posts
for (const post of posts) {
await db.posts.delete({
where: { id: post.id, userId }
});
}
// Finally, delete the user
await db.users.delete({
where: { id: userId, email }
});
}Note: CosmosDB doesn't support foreign key constraints or cascade deletes at the database level. You must handle this logic in your application code.
Bulk Operations
For deleting multiple documents at once, use deleteMany:
// Delete old posts
const result = await db.posts.deleteMany({
where: { createdAt: { lt: oneYearAgo } },
confirm: true, // Safety: must explicitly confirm
partitionKey: 'user123',
onProgress: (stats) => {
console.log(`Deleted ${stats.updated}/${stats.total}`);
}
});
console.log(`Deleted ${result.deleted} documents`);
console.log(`Failed: ${result.failed}`);
console.log(`RU consumed: ${result.performance.ruConsumed}`);Key Options:
where: Query to match documentsconfirm: Must betrueto execute (safety requirement)partitionKeyorenableCrossPartitionQuery: Required (one or the other)batchSize: Documents per batch (default: 50)maxConcurrency: Parallel batches (default: 5)continueOnError: Keep going if some fail (default: false)onProgress: Progress callback with stats
Result includes:
deleted: Number of successfully deleted documentsfailed: Number of failed deletionserrors: Array of error detailsperformance: RU consumption, duration, and throughput metrics
Best Practices:
- Always use
confirm: true- This prevents accidental deletions - Start with small batches when testing (e.g.,
batchSize: 10) - Use
continueOnError: truefor large operations where some failures are acceptable - Monitor progress using
onProgresscallbacks - Use partition keys when possible (much faster than cross-partition queries)
Note: For deleting documents by specific IDs in the same partition, you can still use individual deletes:
async function deleteManyPosts(postIds: string[], userId: string) {
const deletePromises = postIds.map(id =>
db.posts.delete({
where: { id, userId }
})
);
await Promise.all(deletePromises);
}However, deleteMany is more efficient for query-based deletions with built-in progress tracking and error handling.
Bulk operations are built into CosmosQL and work seamlessly with your existing containers.
Delete by Query
For query-based deletions, use deleteMany instead of manually querying and deleting:
// ✅ Recommended: Use deleteMany
const result = await db.posts.deleteMany({
where: {
createdAt: { lt: cutoffDate },
isPublished: false
},
confirm: true,
enableCrossPartitionQuery: true,
onProgress: (stats) => {
console.log(`Progress: ${stats.percentage}%`);
}
});This is more efficient than querying and deleting individually, as it includes built-in progress tracking, error handling, and retry logic.
Error Handling
try {
await db.users.delete({
where: { id: 'user_123', email: 'john@example.com' }
});
} catch (error) {
if (error.code === 404) {
// Document not found
console.error('User not found');
} else if (error.code === 429) {
// Rate limit exceeded
console.error('Rate limit exceeded, please retry');
} else {
console.error('Failed to delete user:', error);
}
}Performance Considerations
1. Always Include Partition Key
// ✅ Good: Includes partition key (fast, ~5 RU)
await db.users.delete({
where: { id: 'user_123', email: 'john@example.com' }
});2. Use Soft Delete for Frequent Deletions
Hard deletes are expensive if done repeatedly. Soft deletes allow you to batch hard deletes later:
// Mark as deleted immediately (fast)
await db.users.update({
where: { id: 'user_123', email: 'john@example.com' },
data: { isDeleted: true, deletedAt: new Date() }
});
// Batch permanent deletion later (background job)
setInterval(async () => {
await cleanupOldSoftDeletes();
}, 60 * 60 * 1000); // Run every hourCommon Patterns
Pattern: Recycle Bin
async function moveToRecycleBin(postId: string, userId: string) {
// Copy to recycle bin
const post = await db.posts.findUnique({
where: { id: postId, userId }
});
await db.recycleBin.create({
data: {
id: `bin_${postId}`,
deletedItemId: postId,
deletedItemType: 'post',
deletedItemData: post,
deletedAt: new Date()
}
});
// Remove from original location
await db.posts.delete({
where: { id: postId, userId }
});
}
async function restoreFromRecycleBin(itemId: string) {
const binItem = await db.recycleBin.findUnique({
where: { id: itemId }
});
if (binItem.deletedItemType === 'post') {
await db.posts.create({
data: binItem.deletedItemData
});
}
await db.recycleBin.delete({ where: { id: itemId } });
}Next Steps
- Read Update Operations for modifying documents
- See Common Patterns for real-world scenarios
- Review Performance Guide for optimization tips