mongoose-model-manager
v1.6.0
Published
ModelManager is a powerful and flexible manager for Mongoose models in Node.js applications. This class simplifies the management of data models, allowing developers to dynamically load and manage Mongoose models.
Downloads
20
Maintainers
Readme
📋 Overview
This module consists of four essential components:
- Model Manager
- Fixture Manager
- Model Utils
- Mongoose Utils
Together, they streamline the management and creation of Mongoose models and MongoDB connections in Node.js, providing a comprehensive framework for:
Dynamic Model Creation: Seamlessly generate Mongoose models on the fly, making it easier to scale and adapt to changing data structures.
In-Memory Testing: If needed utilize Models with the mongodb-memory-server for running tests with isolated in-memory databases. This approach ensures fast, reliable, parallel and deterministic tests.
Multi-Tenant Database Management: Manage multiple databases dynamically, enabling multi-tenancy support by handling distinct connections for different client environments effortlessly.
By combining FixturesManager
and ModelManager
, this framework empowers developers to achieve high performance, scalability, and modularity. It optimizes development workflows by automating model creation and fixture handling, which enhances both testing efficiency and overall application reliability.
Model Manager 📦
The Model Manager is the core class of this package and facilitates dynamic management of Mongoose models in a Node.js application. It simplifies loading models from specified directories, making it ideal for multi-tenant configurations. The class enhances interactions with Mongoose, allowing for efficient model management and ensuring consistent schema definitions, promoting scalability and maintainability.
📄 Usage
1. Define MongoDB Connection String
Make sure to load environment variable MONGODB_CONNECTION_STRING
:
MONGODB_CONNECTION_STRING=mongodb://root:[email protected]:30644/?authSource=admin
- You can use dependencies like: dotenv
2. Initializing the Model Manager
The ModelManager
is a singleton class. To get the instance and initialize it, use the following code:
import { ModelManager } from 'mongoose-model-manager'
const modelManager = await ModelManager.getInstance()
The ModelManager
automatically globs for all *.model.mjs
files in your project. Ensure your model files are named correctly for them to be recognized and follow this exported format:
const modelName = 'testModelName'
const dbName = 'testDbName'
const schema = {
name: {
type: String,
required: true,
unique: true,
index: true
},
decimals: { type: BigInt, required: true }
}
export { dbName, modelName, schema }
- The model name will be used aswell for the collection name
Methods 📜
.getInstance()
- Retrieves the singleton instance of the ModelManager. Initializes the instance on the first call.
/**
* @returns {Promise<ModelManager>} The singleton instance.
*/
public static async getInstance(): Promise<ModelManager>
import { ModelManager } from 'mongoose-model-manager'
const modelManager = await ModelManager.getInstance()
.getModel()
- Retrieves a Mongoose model by its name. Throws an error if the model is not found.
/**
* @param {string} name - The model name.
* @returns {IModel<any>} The Mongoose model or throws an error.
* @throws {ResourceNotFoundError} If the model is not found.
*/
public getModel(name: string): IModel<any>
import { ModelManager } from 'mongoose-model-manager'
const modelManager = await ModelManager.getInstance()
const model = modelManager.getModel(modelName)
.getModels()
- Returns all loaded Mongoose models.
/**
* @returns {IModel<any>[]} Array of loaded models.
*/
public getModels(): IModel<any>[]
import { ModelManager } from 'mongoose-model-manager'
const modelManager = await ModelManager.getInstance()
const models = modelManager.getModels()
.createModel()
- Create a new mongoose model and push it to the instance
/**
* @template IMongooseSchema - The schema type.
* @param {IModelCore} modelDetails - Object containing details for creating the model.
* @param {string} modelDetails.modelName - The name of the model being created.
* This will be used as the identifier within the application and MongoDB collection.
* @param {mongoose.Schema<IMongooseSchema>} modelDetails.schema - The Mongoose schema defining the structure
* and validation rules for the model. It dictates how documents in the collection will be structured.
* @param {string} modelDetails.dbName - The name of the database where the model will be stored.
* This is essential for multi-tenant applications or cases where multiple databases are managed.
*
* @returns {Promise<mongoose.Model<IMongooseSchema>>} The created Mongoose model.
*
*/
public async createModel<IMongooseSchema>({
modelName,
schema,
dbName
}: IModelCore): Promise<mongoose.Model<IMongooseSchema>>
import { ModelManager } from 'mongoose-model-manager'
const modelManager = await ModelManager.getInstance()
interface IUser {
name: string
email: string
}
const schema = {
name: String,
email: String
}
const newModel = await modelManager.createModel<IUser>({
modelName: 'NewModel',
dbName: 'YourDatabaseName',
schema
})
🔗 Model Utils
The Model Utils class specializes at the moment in creating in-memory models using MongoDB Memory Server but it will be maybe extended in the future. This utility is crucial for parallel testing scenarios that require transient databases, allowing developers to create isolated environments that mimic real MongoDB behavior without persistent storage overhead.
Methods 📜
.createMemoryModel()
- Creates an in-memory Mongoose model, connecting it to an in-memory MongoDB instance. This is particularly useful for unit/integration testing without needing to spin up a real MongoDB server
/**
* @template IMongooseSchema - The TypeScript type representing the Mongoose schema.
* @param {IModelCore} modelCoreDetails - The core model details including schema and model name.
* @returns {Promise<IMemoryModel<IMongooseSchema>>} - Promise resolving to an in-memory model with its connection.
*/
public static async createMemoryModel<IMongooseSchema>(
modelCoreDetails: IModelCore
): Promise<IMemoryModel<IMongooseSchema>>
import { ModelUtils } from 'mongoose-model-manager'
interface IUser {
name: string
email: string
}
const schema = {
name: String,
email: String
}
const modelDetails = {
dbName: 'YourDatabaseName',
modelName: 'YourModelName',
schema
}
const memoryModel = await ModelUtils.createMemoryModel<IUser>(modelDetails)
const UserModel = memoryModel.Model
// Creating a new user
const newUser = await UserModel.create({
name: 'John Doe', email: '[email protected]'
});
// Fetching all users
const users = await UserModel.find({});
Response
The created in-memory model will return an object containing the following:
- Model: The Mongoose model instance.
- mongoServer: The
MongoMemoryServer
instance that manages the in-memory database. Please check the docs of MongoDB Memory Server for all options. - conn: The Mongoose connection instance to the in-memory database.
- mongoUri: - The MongoDB Connection String
🔗 Mongoose Utils
The Mongoose Utils class offers tools for managing and creating Mongoose connections and schemas, optimized for multi-tenant databases. Employing the Singleton design pattern, it ensures a single instance per database for efficient resource management.
However, this serves more as a utility class, and everything you require will be encompassed and managed by ModelManager
.
.getInstance()
- Retrieves the singleton instance of
MongooseUtils
for a specific database. Initializes the instance on the first call.
/**
* @param {string} dbName - Name of the database to connect.
* @returns {MongooseUtils} The instance managing the connection to the database.
*/
public static getInstance(dbName: string): MongooseUtils
import { MongooseUtils } from 'mongoose-model-manager'
const mongooseUtils = MongooseUtils.getInstance('YourDatabaseName')
.getConnection()
- Retrieves the MongoDB connection, initializing it if not already connected.
/**
* @returns {Promise<mongoose.Connection>} The established MongoDB connection.
*/
public async getConnection(): Promise<mongoose.Connection>
import { MongooseUtils } from 'mongoose-model-manager'
const mongooseUtils = MongooseUtils.getInstance('YourDatabaseName')
const conn = await mongooseUtils.getConnection()
.createSchema()
- Creates a new Mongoose schema for a model.
**
* @template IMongooseSchema - Interface representing the Mongoose schema.
* @param {mongoose.SchemaDefinition} schema - The schema definition for the model.
* @param {mongoose.SchemaOptions<any>} options - Schema options such as timestamps or collection name.
* @returns {mongoose.Schema<IMongooseSchema>} The constructed Mongoose schema.
*
* @see https://mongoosejs.com/docs/guide.html#options for options.
*/
public static createSchema<IMongooseSchema>(
schema: mongoose.SchemaDefinition,
options: mongoose.SchemaOptions<any>
): mongoose.Schema<IMongooseSchema>
import { MongooseUtils } from 'mongoose-model-manager'
interface IUser {
name: string
email: string
}
const schema = {
name: String,
email: String
}
const model = await MongooseUtils.createSchema<IUser>(
schema,
{ collection: 'test' }
)
.createModel()
- Creates a Mongoose model for a specific schema and collection. This method connects to the database, defines a schema, and initializes a model.
/**
* @template IMongooseSchema - Interface representing the schema definition.
* @param {mongoose.SchemaDefinition} schema - The schema to define the model.
* @param {string} modelName - The name of the model and corresponding MongoDB collection.
* @returns {Promise<mongoose.Model<IMongooseSchema>>} The initialized Mongoose model.
*/
public async createModel<IMongooseSchema>(
schema: mongoose.SchemaDefinition,
modelName: string
): Promise<mongoose.Model<IMongooseSchema>>
import { MongooseUtils } from 'mongoose-model-manager'
const mongooseUtils = MongooseUtils.getInstance('YourDatabaseName')
interface IUser {
name: string
email: string
}
const schema = {
name: String,
email: String
}
const model = await mongooseUtils.createModel<IUser>(
schema,
modelName
)
🛠️ FixturesManager
- This project provides a Singleton pattern-based
FixturesManager
to handle fixtures for MongoDB in-memory database testing, specifically for Mongoose models. It's designed to load, insert, and clean up test data efficiently, supporting integration with mongodb-memory-server and automated fixture loading from file system patterns.
✨ Features
- Singleton Pattern: Ensures only one instance of the manager exists.
- MongoDB Memory Server: Utilizes
MongoMemoryServer
to create an isolated in-memory database for testing. - Mongoose Model Integration: Automatically loads Mongoose models and fixtures.
- Glob Support: Loads fixture files using glob patterns.
- Automatic Cleanup: Cleans up databases and stops memory servers after tests.
- Error Handling: Built-in error validation for duplicate fixture IDs and missing data.
Initializing the Model Manager
The FixturesManager
is a singleton class. To get the instance and initialize it, use the following code:
import { FixturesManager } from 'mongoose-model-manager'
const modelManager = await ModelManager.getInstance()
The FixturesManager
automatically globs for all *.ts
files at the path test/fixtures
inside of your project root. Ensure your fixtures are following this exported format:
import mongoose from 'mongoose'
const name = 'Example test fixture only for unit tests'
const docContents = {
_id: new mongoose.Types.ObjectId('000000000000000000000001'),
name: 'Unit Test fixture',
decimals: 18n,
__v: 0
}
export { docContents, name }
Example inserting and cleaning fixtures
import { FixturesManager } from 'mongoose-model-manager'
import {
beforeAll,
beforeEach, afterEach,
describe, it
} from 'vitest'
let FIXT_NewPairs
const modelName = 'ETH.NewPairs'
const ID_NewPairs = '000000000000000000000001'
beforeAll(async () => {
const fixturesManager = await FixturesManager.getInstance()
FIXT_NewPairs = fixturesManager.getFixture(ID_NewPairs)
})
afterEach(async() => {
await fixturesManager.clean([ID_NewPairs])
})
describe('[EXAMPLE TEST CASE]', () => {
beforeEach(async() => {
const fixtures = await fixturesManager.insert([ID_NewPairs])
const { Model } = fixtures[ID_NewPairs]
getModelStub = sinon.stub(ModelManager.prototype, 'getModel').resolves({
Model
})
})
it('should return columns, coins, statusOptions because coins are found', async() => {
const result = await fetchNewPairs()
expect(getModelStub.calledOnce).toBe(true)
expect(result.coins[0].value).toEqual(FIXT_NewPairs.doc.value)
})
})
🧰 Methods
getInstance()
- Retrieves the singleton instance of the manager. Initializes it on the first call.
public static async getInstance(): Promise<FixturesManager>
import { FixturesManager } from 'mongoose-model-manager'
const fixturesManager = await FixturesManager.getInstance()
insert()
- Inserts specified fixtures into the in-memory MongoDB instance and returns their inserted state.
/**
* @param {string[]} ids - Array of fixture IDs to insert.
* @returns {Promise<Record<string, IFixture>>} The inserted fixture data.
* @throws {ValidationError} If the fixture has already been inserted.
* @throws {ResourceNotFoundError} If the fixture ID is not found.
*/
public async insert(
ids: string[]
): Promise<{ [id: string]: IFixture }>
import { FixturesManager } from 'mongoose-model-manager'
const fixturesManager = await FixturesManager.getInstance()
const result = await fixturesManager.insert([fixtureId])
Response
The inserted fixture will return object({ [id: string]: IFixture }
) containing the following properties:
name
: Identifier for the fixture.docContents
: The actual data which is stored in your fixture filedoc
: The inserted Mongoose document.docLean
: The lean (plain object) version of the document.docToObject
: The document as a plain JavaScript object.Model
: The Mongoose memory model used for insertion.mongoServer
: The in-memory MongoDB instance.
getFixture(id: string)
- Fetches a fixture by its ID. Throws a
ResourceNotFoundError
if not found.
/**
* @param {string} id - The ID of the fixture to retrieve.
* @returns {IFixtureDoc | IFixtureInserted} The fixture object.
* @throws {ResourceNotFoundError} If the fixture ID is not found.
*/
public getFixture(id: string): IFixtureDoc | IFixtureInserted
import { FixturesManager } from 'mongoose-model-manager'
const fixturesManager = await FixturesManager.getInstance()
await fixturesManager.insert(fixtureId)
const fixture = await fixturesManager.getFixture(fixtureId)
clean()
- Cleans up specific fixtures and stops associated MongoMemoryServer instances. This method iterates through the specified fixture IDs, stopping their associated MongoMemoryServer instances if they exist and removing the fixtures from memory.
/**
* @param {string[]} ids - Array of fixture IDs to clean up.
* @returns {Promise<void>} Resolves when cleanup is complete.
*/
public async clean(ids: string[]): Promise<void>
import { FixturesManager } from 'mongoose-model-manager'
const fixturesManager = await FixturesManager.getInstance()
const fixture = await fixturesManager.insert(fixtureId)
await fixturesManager.clean([fixtureId])
cleanAll()
- Cleans up all fixtures and stops associated MongoMemoryServer instances. This method iterates through all fixtures stored in memory, stopping their associated MongoMemoryServer instances and clearing the fixtures from memory.
/**
* @returns {Promise<void>} Resolves when cleanup is complete.
*/
public async cleanAll(): Promise<void>
import { FixturesManager } from 'mongoose-model-manager'
const fixturesManager = await FixturesManager.getInstance()
const fixture = await fixturesManager.insert(fixtureId)
const fixture2 = await fixturesManager.insert(fixtureId2)
await fixturesManager.cleanAll()