Skip to content

Auto-Increment Sequence

MongoDB’s default _id is a 12-byte ObjectId, which is ideal for uniqueness in distributed systems. However, some applications require a simple, human-readable auto-incrementing integer sequence (e.g., 1, 2, 3…). MongoDB does not provide this out-of-the-box, but you can implement it using an atomic operation on a dedicated ‘counters’ collection.

Before implementing, consider the trade-offs: While simple, this method requires an extra database call for every insert and can become a write bottleneck on a sharded cluster if not designed carefully. For most cases, the default ObjectId is the better choice.

The strategy involves two steps:

  1. Create a counters collection to store the last sequence value for each key you want to auto-increment.
  2. Create a function in your application that atomically finds and increments the counter, then returns the new value.

Step 1: Initialize the Counters Collection

Section titled “Step 1: Initialize the Counters Collection”

First, from the MongoDB Shell (mongosh), insert a document to track our productId sequence. We’ll start the sequence at 0.

// Connect to your database
use my_app_db;
// Insert the initial counter document
db.counters.insertOne({
_id: "productId",
sequence_value: 0
});

Creating an Application-Side Function (Node.js Example)

Section titled “Creating an Application-Side Function (Node.js Example)”

Instead of creating a function inside the database (a legacy practice), you should implement this logic in your application code. This is more maintainable, scalable, and secure. Here is an example using Node.js and the official mongodb driver.

const { MongoClient } = require('mongodb');
// Replace with your connection string
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);
/**
* Gets the next value in a sequence.
* @param {Db} db The database instance.
* @param {string} sequenceName The name of the sequence (e.g., 'productId').
* @returns {Promise<number>} The next sequence value.
*/
async function getNextSequenceValue(db, sequenceName) {
const sequenceDocument = await db.collection('counters').findOneAndUpdate(
{ _id: sequenceName },
{ $inc: { sequence_value: 1 } },
{ returnDocument: 'after' } // Ensures the updated document is returned
);
if (!sequenceDocument) {
throw new Error(`Sequence '${sequenceName}' not found. Please initialize it.`);
}
return sequenceDocument.sequence_value;
}
// --- Main Application Logic ---
async function main() {
try {
await client.connect();
const db = client.db('my_app_db');
const productsCollection = db.collection('products');
// Create a new product using the sequence
const newProduct = {
_id: await getNextSequenceValue(db, 'productId'),
product_name: 'Super High-Tech Widget',
category: 'electronics'
};
const result = await productsCollection.insertOne(newProduct);
console.log(`Successfully inserted new product with _id: ${result.insertedId}`);
// Create another one
const anotherProduct = {
_id: await getNextSequenceValue(db, 'productId'),
product_name: 'Another Cool Gadget',
category: 'electronics'
};
const anotherResult = await productsCollection.insertOne(anotherProduct);
console.log(`Successfully inserted new product with _id: ${anotherResult.insertedId}`);
} catch (e) {
console.error('An error occurred:', e);
} finally {
await client.close();
}
}
main();

This modern approach uses findOneAndUpdate which is an atomic operation, ensuring that even with concurrent requests, each call will receive a unique, incremented value. The returnDocument: 'after' option is crucial for getting the new value back.