How to Use Enums in Mongoose: A Practical Guide

Updated: December 30, 2023 By: Guest Contributor Post a comment

In the world of NoSQL databases, Mongoose serves as a robust modeling tool for MongoDB, assisting developers in crafting clear schemas with typed data. This guide is tailored to help you adeptly use enumerable fields (enums) within your Mongoose schema definitions.

What are Enums?

Enums, or enumerations, are a feature in many programming languages, including JavaScript and TypeScript, that allow you to define a set of named constants. Using enums ensures that a variable must have one of a limited set of values, which can be especially useful in a database schema context to enforce data integrity.

In Mongoose, enums can be utilized within schema definitions to restrict the values accepted by a particular field. For example, if you have a field that should only accept ‘Yes’, ‘No’, or ‘Maybe’ as valid inputs, you can use an enum to enforce this restriction.

Implementing enums in Mongoose schemas leads to more predictable and secure data by reducing typographical errors and limiting the scope of inputs to the expected set of values.

Setting Up a Basic Enum in Mongoose

We begin by defining a Mongoose schema with a field that should only accept a predefined set of values.

// require the mongoose module
const mongoose = require('mongoose');

// create a new schema using the Schema constructor
const { Schema } = mongoose;

// define the schema
const exampleSchema = new Schema({
  status: {
    type: String,
    enum: ['Open', 'Closed', 'Pending'],
    default: 'Pending'
  }
});

// create a model from the schema
const ExampleModel = mongoose.model('Example', exampleSchema);

This block defines a schema for a collection that contains a status field, which can only be one of ‘Open’, ‘Closed’, or ‘Pending’. The default value for new documents will be ‘Pending’.

When saving a document, Mongoose will validate the status field against the provided enum values.

Advanced Enum Patterns

For more complex scenarios, we might want to further explore enum capabilities within Mongoose. This may involve creating dynamic enums based on other values or integrating with TypeScript for stronger type safety and autocompletion.

In some cases, you might want to define your enums separately or even use numerical values, particularly if you are working with TypeScript. Here’s how you might implement such patterns.

// Defining enums separately
const StatusEnum = Object.freeze({
  OPEN: 'Open',
  CLOSED: 'Closed',
  PENDING: 'Pending'
});

const exampleSchema = new Schema({
  status: {
    type: String,
    enum: Object.values(StatusEnum),
    default: StatusEnum.PENDING
  }
});

// TypeScript example using numerical enums
enum StatusCode {
  Open = 1,
  Closed,
  Pending
}

const exampleSchema = new Schema<{
  status: StatusCode;
}>({ status: {
      type: Number,
      enum: Object.values(StatusCode).filter(value => typeof value === 'number'),
      default: StatusCode.Pending
    }
});

Using separate enum objects or TypeScript enumeration types helps to standardize usage across your codebase, reduce the risk of errors, and improve maintainability.

Error Handling for Enum Validations

When inserting or updating documents in Mongoose, your operations will fail if the data does not comply with the enum definitions. It’s essential to handle these errors gracefully.

The following example demonstrates how to handle such validation errors when saving a document.

const exampleDocument = new ExampleModel({ status: 'InvalidStatus' });
exampleDocument.save().catch(err => {
 if(err.name === 'ValidationError') {
   console.error(`Error: Status must be one of ${Object.values(StatusEnum).join(', ')}.`);
 } else {
   throw err;
 }
});

Here, if an invalid status is provided, Mongoose throws a ValidationError, which we catch and process accordingly.

Integrating Enums with TypeScript

The synergy between Mongoose and TypeScript can be harnessed to provide a stronger type system. Here’s how to use TypeScript Enums with Mongoose.

The first step is to define a TypeScript enum matching the allowed values in the schema.

enum TaskStatus {
  Todo = 'TODO',
  InProgress = 'IN_PROGRESS',
  Done = 'DONE'
}

interface ITask {
  status: TaskStatus;
}

const taskSchema = new mongoose.Schema({ 
  status: { 
    type: String, 
    enum: Object.values(TaskStatus), 
    default: TaskStatus.Todo 
  }
});

const Task = mongoose.model('Task', taskSchema);

With such integration, you gain the ability to utilize TypeScript’s compile-time checks for additional robustness.

Conclusion

The use of enums in Mongoose schemas is vital for ensuring data integrity and providing a clear contract for the data model. It helps reduce bugs and eases communication about valid field values between developers and stakeholders. By understanding these patterns and their implementation using JavaScript or TypeScript, you can rest assured that your Mongoose models are robust and maintain the highest standards in data typing.