How to handle Enums in Mongoose with TypeScript

Mongoose doesn't have built-in support for TypeScript enums, but learn how this is possible, and how it helps enforce data integrity and provides type checking during development

  • ERT:

On the journey of building GISU Rest API - initialy, built, just to handle posting at the School of Education, Gambia College - we want to ensure that only valid enums values can be assigned to certain fields. This helps us enforce data integrity and provides type checking during development.

But…

Mongoose doesn’t have built-in support for TypeScript enums, and it really can’t because TypeScript enums are just POJOs at runtime. But one thing we should do is eliminate the need for the Object.values() part. (Mastering JS )

One of the enums we use, was to let students select the region of the school they want to be posted at. We don’t want them to type this data due the fact tha we will different inputs from the students. So enums to the rescue.

That said, let’s look at how we have implemented this:

Enum definition

First, we define the Enum:

// Region enum definition
enum ERegion {
  r1 = 'Region 1', 
  r2 = 'Region 2', 
  r3 = 'Region 3', 
  r4 = 'Region 4', 
  r5 = 'Region 5', 
  r6 = 'Region 6', 
}

Note the following:

We use the following naming convention as far as enums, interfaces and types are concern:

  • interfaces start with capital I
  • types starts with capital T and
  • enums starts with a capital E

This is why the name of the region enum is: ERegion and not just Region.

Use the enum in Mongoose schema

Second, we use the enum defined above in our schema.

import { Schema, model, Types } from 'mongoose';

// Omitted enum and interface definition

// Document schema definition
const regionSchema = new Schema<IRegion>(
  {
   	// omitted codes
    name: {
      type: String,
      required: true,
      unique: true,
      enum: Object.values(ERegion) // Set the enum values to the valid options from the ERegion enum
    },

    created_by: {
      type: Schema.Types.ObjectId,
      ref: 'user',
      required: true
    }
  },
  { timestamps: true }
);

// Create a Model.
const Region = model<IRegion>('region', regionSchema);

// Export the model
export default Region;

Here’s that line: enum: Object.values(ERegion) inside the name object.

enums with a default value

In some of our cases, we have to use enums with a default value and here’s an example of that:

import { Schema, model, Types } from 'mongoose';

// instruction mode enum definition
enum EInstructionsMode {
  lecture = 'Lecture',
  posting = 'Posting',
}

// Omitted interface definition

// Document Schema
const courseSchema = new Schema<ICourse>(
  {
   // omitted codes
    instructions_mode: {
      type: String,
      required: true,
      enum: Object.values(EInstructionsMode),
      default: EInstructionsMode.lecture // default to "Lecture"  
    },

    created_by: {
      type: Schema.Types.ObjectId,
      ref: 'user',
      required: true
    }
  },
  { timestamps: true }
);

// Create a Model.
const CourseModel = model<ICourse>('course', courseSchema);

// Export the model
export default CourseModel;

Here’s that line: default: EInstructionsMode.lecture inside the instructions_mode object.

Note: Enums in Mongoose are stored as strings in the database, so make sure to use string values when defining the enum options.

Extracting enums to their own module

When you are working on a medium or large scale project, we highly recommend you to extract all your enums in your code base to one enums directory.

Within the src of your project create a folder called enums and a file called index.ts (if using typescript, otherwise, index.js)

This has the following benefits:

  • Single source of truth
  • Re-usability of enums winthin and across projects
  • ….

As a general practice it’s good to have an index.js file which exports the enums so you don’t have to change import paths or have repetitive ones.

For example if we are going to create an enums for regions and user roles. We can create the two files within the enums directory: regions.js and user-roles.js.

regions.js

// Region enum definition
enum ERegions {
  r1 = 'Region 1', 
  r2 = 'Region 2', 
  r3 = 'Region 3', 
  r4 = 'Region 4', 
  r5 = 'Region 5', 
  r6 = 'Region 6', 
}

export default ERegions;

user-roles.js

// User role enum definition
enum EUserRoles {
  super_admin = 'Super Admin', 
  admin = 'Admin', 
  user = 'User', 
}

export default EUserRoles;

Then finally, in the index.js you can export them:

export { default as ERegions } from './ERegions';
export { default as EUserRoles } from './EUserRoles';
// more enums

And the beauty is, you can easily import enums with one line from the same drectory like this:

import { ERegions, EUserRoles } from '../enums'

Note: keep the component file with its name so you don’t get confused when you have multiple ones open.

Related Journals

    No related journal

    No related journal

    No related journal