Sling Academy
Home/Node.js/How to create auto-incrementing field in Mongoose

How to create auto-incrementing field in Mongoose

Last updated: December 30, 2023

Introduction

Building applications with databases often necessitates the use of unique identifiers that increment with each new record. In SQL databases, this is typically handled out of the box. However, in MongoDB, which Mongoose is built to interface with, such a feature isn’t provided by default. In this tutorial, we’ll explore how to implement an auto-incrementing field in a Mongoose schema by using several code examples of increasing complexity.

Auto-increment can be particularly useful when we want to create user-friendly identifiers for records that are easier to refer to than the default ObjectId that MongoDB generates. We’ll discuss how you can create auto-incrementing fields in Mongoose schemas for performing functionalities like keeping track of the number of users, items, or a sequential order of records in any collection.

Before we begin, make sure you have:

  • Node.js and npm (Node Package Manager) installed
  • A basic understanding of JavaScript or TypeScript’s latest syntax
  • Some experience working with Mongoose over MongoDB

Setup

Let’s start with a basic Mongoose setup:

import mongoose from 'mongoose';

mongoose.connect('mongodb://localhost/exampleDb', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

Auto-Increment with Mongoose Plugin

To begin, we’ll look into using a plugin that can be added to any schema to provide auto-increment functionality:

import mongoose from 'mongoose';
import autoIncrement from 'mongoose-auto-increment';

const connection = mongoose.createConnection('mongodb://localhost/exampleDb');

autoIncrement.initialize(connection);

const UserSchema = new mongoose.Schema({
  //... other fields ...
  userId: Number
});

UserSchema.plugin(autoIncrement.plugin, 'User');
const User = connection.model('User', UserSchema);
// `userId` will auto-increment

This is a great starting point and perfect for relatively simple use cases. However, it doesn’t allow much customization and may be limiting as our application grows.

The Custom Counter Approach

For a more flexible solution, one can create a counter schema:

import mongoose from 'mongoose';

const CounterSchema = new mongoose.Schema({
  _id: String,
  seq: Number
});

CounterSchema.methods.increment = async function() {
  this.seq += 1;
  return this.save();
};

const Counter = mongoose.model('Counter', CounterSchema);

Counter.findOneAndUpdate(
  { _id: 'userId' },
  { $inc: { seq: 1 } },
  { new: true, upsert: true }
)
.catch(console.error);

Then, before creating a new User record, we increment the counter to generate a new unique identifier:

const UserSchema = new mongoose.Schema({
  // ... other fields ...
  userId: Number
});

UserSchema.pre('save', async function() {
  if (this.isNew) {
    const counter = await Counter.findOneAndUpdate(
      { _id: 'userId' }, { $inc: { seq: 1 } },
      { new: true, upsert: true }
    );
    this.userId = counter.seq;
  }
});

const User = mongoose.model('User', UserSchema);

This way, the `userId` field increments every time a new User document is saved. This approach gives more control over how counter increments.

Auto-Incrementing Sequence in Transactions

For applications requiring robust transaction support, ensuring that sequences are not duplicated even if documents are not successfully created can be crucial. Transactions can be combined with change streams to lock the counter update process till they’re complete:

//... Initializing transaction ...
const session = await connection.startSession();
session.startTransaction();

// Performing counter increment strictly within a transaction
const opts = { session, new: true };
const counter = await Counter.findOneAndUpdate(
  { _id: 'userId' }, { $inc: { seq: 1 } }, opts
);

try {
  const newUser = User.create([{ userId: counter.seq }], { session: session });
  await session.commitTransaction();
} catch (error) {
  await session.abortTransaction();
  throw error;
} finally {
  session.endSession();
}

This way, even if the User document creation encounters a problem, the userId counter would be incremented safely as a part of the same transaction.

Conclusion

In this tutorial, we have traversed through ways to add auto-increment functionality to your Mongoose models, going from plug-and-play techniques to more complex, custom implementations suitable for production-scale applications. Remember, a comprehensively tested implementation reduces the risk of duplicates or other issues in fields that are expected to auto-increment reliably. Ultimately, what approach you choose will depend on the specific needs and constraints of your application. Happy Coding!

Next Article: Fix: Cannot overwrite model once compiled in Mongoose

Previous Article: Mongoose: How to compare _id with a string value

Series: Mongoose.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates