Mongo Strict is a TypeScript based smart MongoDB ORM, It makes the usage of MongoDB safer, easier and faster with a better performance...

Overview

mongo-strict

Lines Statements Functions Branches

mongo-strict is compatible with mongo >= 5

Mongo Strict is a TypeScript-based smart MongoDB ORM, It makes the usage of MongoDB safer, easier and faster with better performance...

mongo-strict gives you the safety of the SQL DBs with keeping the flexibility and the ease of use of MongoDB.

mongo-strict Allows you to

  • Define the entity data types and ensure the data validity (Using class-validator in the background).
  • Define the relations between the data.
  • Add a default value for any field.
  • Mark any field as required.
  • Get the related data using the reference keys whithout using any complicated lookups.
  • Imporove the App performance by using the best practices in the background.
  • Imporove the code quality.
  • Cache any query for a better performance.

Table of Contents

Instalation

npm install mongo-strict --save

Usage

Example: Suppose we have CVs management system => Every user can create multiple CVs and every CV has multiple sections.

Create your Database connection with the connection URL

import { createConnection } from 'mongo-strict';

await createConnection({
    uri: `mongodb://localhost:27017/fancy-cvs`
});

Define your DB Repositories:

import { addRepository, Entity, IsRequired, IsUnique, ORMOperations, Allow, IsEmail, MinLength, IsString, IsArray, RefersTo } from 'mongo-strict';

@Entity({ name: 'user' })
class UserEntity {
    @Allow()
    @IsEmail(undefined, { message: "The email should be valid :(" })
    @IsUnique({ isIgnoreCase: true })
    email: string;

    @Allow()
    @IsRequired()
    @MinLength(3)
    name: string;

    @Allow()
    @IsString()
    country: string;

    @Allow()
    @IsArray()
    @RefersTo({
        collection: 'cv',
        key: 'id',
        isArray: true
    })
    cvs: any[];
}

export class UserRepository extends ORMOperations {
    constructor() {
        const ORM = addRepository(UserEntity, { debug: false });
        super(ORM);
    }
}
import { addRepository, Entity, IsRequired, ORMOperations, RefersTo, IsString, Allow, IsArray } from 'mongo-strict';

@Entity({ name: 'cv' })
class CVEntity {
    @Allow()
    @IsRequired()
    @IsString()
    cvName: string;

    @Allow()
    @IsRequired()
    @IsString()
    currentPosition: string;

    @Allow()
    @IsArray()
    @RefersTo({
        collection: 'section',
        key: 'id',
        isArray: true
    })
    sections: any[]
}

export class CVRepository extends ORMOperations {
    constructor() {
        const ORM = addRepository(CVEntity);
        super(ORM);
    }
}
import { addRepository, Entity, IsRequired, ORMOperations, Allow, IsString } from 'mongo-strict';

@Entity({ name: 'section' })
class SectionEntity {
    @Allow()
    @IsRequired()
    @IsString()
    sectionTitle: string;
}

export class SectionRepository extends ORMOperations {
    constructor() {
        const ORM = addRepository(SectionEntity);
        super(ORM);
    }
}

Then you are ready to start...

import { createConnection, initDBMap } from 'mongo-strict';
import { SectionRepository } from './section.repository';
import { CVRepository } from './cv.repository';
import { UserRepository } from './user.repository';

const start = async () => {
    await createConnection({
        uri: `mongodb://localhost:27017/fancy-cvs`
    });

    const userRepository = new UserRepository();
    const cvRepository = new CVRepository();
    const sectionRepository = new SectionRepository();

    // Should be called after initializing all the repositories
    initDBMap();

    let insertedUser;
    try {
        // You don't need to make any check before inserting or updating, mongo-strict will do that.
        insertedUser = await userRepository.insertOne({
            email: '[email protected]',
            name: 'mongo user',
            country: 'mongolia',
            cvs: []
        });
    } catch (e) { }

    let insertedCV;
    if (insertedUser) {
        try {
            insertedCV = await cvRepository.insertOne({
                cvName: 'User CV 1',
                currentPosition: 'Developer !',
                sections: []
            });
            await userRepository.update(insertedUser.id).setOne({ cvs: [insertedCV.id] });
        } catch (e) { }
    }

    if (insertedCV && insertedUser) {
        const insertedSections: any = [];
        for (let i = 0; i < 6; i++) {
            try {
                const insertSection = await sectionRepository.insertOne({
                    sectionTitle: `Section ${i + 1}`
                });
                insertedSections.push(insertSection);
            } catch (e) { }
        }

        await cvRepository.update(insertedCV.id).setOne({ sections: insertedSections.map((section) => section.id) });
    }

    // This will fetch the user cvs and section with no need to make any lookups
    const userData = await userRepository.findOne({
        select: ["id", "name", "cvs.cvName", "cvs.currentPosition", "cvs.sections.sectionTitle"]
    })

    console.log(JSON.stringify(userData, null, 4));
}

start();

You can check more examples in the samples folder

Create Connection

You should pass the connection options which should contains the connection uri.

You can pass the default repository Options which will be applied to the all repositories.

await createConnection({
    uri: `mongodb://localhost:27017/fancy-cvs`
}, repositoryOptions);

Repository Options

Add Repository

You can add a new repository by calling:

addRepository(EntityClass, repositoryOptions)

Repository Options

Option Description
autoCreatedAt default true
autoUpdatedAt default true
createdAtKey default 'createdAt'
updatedAtKey default 'updatedAt'
maxFindTimeMS default 60000
debug default false
defaultSelectFields default undefined
cacheTimeout default 1000 MS
entityClassValidator Entity Class Validator Options (defaults: {whitelist: true, forbidNonWhitelisted: true, validationError: { target: false }})
reverseRefering Determine if you want to be able to select a reference from the refers to collection (default : false), BE CAREFUL BEFORE ENABLING THIS BECAUSE IT MAY AFFECT YOUR APP PERFORMANCE

Entity Class

You should pass the @Entity decorator before the Entity class and pass the collection name as a variable.

The entity class should contains all the entity keys.

You can add validations to every key and determine the default value, uniqueness and the references.

@Entity({ name: 'user' })
class UserEntity {
    @Allow()
    @IsEmail(undefined, { message: "The email should be valid :(" })
    @IsUnique({ isIgnoreCase: true })
    email: string;

    @Allow()
    @IsRequired()
    @MinLength(3)
    name: string;
}

Entity Validation

class-validator

We use class-validator to validate the Entities

So you can call any validator class-validator provides, Examples:

  @Length(10, 20)
  @Contains('hello')
  @IsInt()
  @Min(0)
  @Max(10)
  @IsEmail()
  @IsFQDN()
  @IsDate()

IsRequired

You can mark any key as a required and pass the error message which will be passed if the key is not found.

@IsRequired({message: 'This Key is required'})
requiredKey;

IsUnique

You can mark any key as unique key through the collection.

You can determine if you need it case sensitive or not.

@IsUnique({message 'The use email should be unique', isIgnoreCase: true}) // isIgnoreCase default false
userEmail;

Default

You can pass the default value of any key

@Default(0)
@IsNumber()
counter;

RefersTo

You can mark any key as a reference key.

@RefersTo({
    collection: 'user'.
    key: 'id',
    as: 'user'
})
user;

RefersTo Options

Option Description
collection The collection which the key refers to
key The key which the refer key refers to
as Select the reference as (defaults to the collection name)
isArray Determine if the key is an array (for example we may have array of users refer to many users with different Ids) (default false)
reverseRefering Determine if want to be able to select the current collection from the refers to collection (default false)
reverseReferingAs Select the current key form the refers to collection as
maxDepth Max Depth in case of circular references
type The relation type => RELATION_TYPES.ONE_ONE - RELATION_TYPES.ONE_TO_MANY - RELATION_TYPES.MANY_TO_ONE - RELATION_TYPES.MANY_TO_MANY (default many to one)
message The error message in case of insert or update refers to entity not found

Initialize the DB Map

You should call initDBMap() function after initializing all the repositories to inialize your database reference Map, Example:

    await createConnection({
        uri: `mongodb://localhost:27017/fancy-cvs`
    });

    const userRepository = new UserRepository();
    const cvRepository = new CVRepository();
    const sectionRepository = new SectionRepository();

    // Should be called after initializing all the repositories
    initDBMap();

    // You can find the complete example in the Samples folder

Operations

mongo-strict supports the main Database operations and you can get the original collection for any operation we do not support until now.

find(findOptions: FindOptions)

To make a find query you have to pass the find options object which can contain where, select, sort...

FindOptions

findOption Description
where Filters the documents to pass only the documents that match the specified condition(s). (mongodb aggregation $match)
select determine the field you want to select (can be array of strings or mongodb aggregation $project)
sort returns the documents in sorted order (mongodb aggregation $sort)
limit Limits the number of the returned documents (mongodb aggregation $limit)
skip Skips over the specified number of documents (mongodb aggregation $skip)
debug true or false to print the final lookup DB method in the console, default = false

find example

suppose we have a collection of users and we want to get the email of the latest 10 users from a specific country...

// returns array of documents
const usersEmail = await userRepository.find({
    where: {country: "Mongolia"},
    sort: {createdAt: -1},
    limit: 10,
    skip: 0,
    select: ["email", "id"]
})

findAndCount(findOptions: FindOptions)

const {data, count} = await userRepository.findAndCount({
    where: {country: "Mongolia"},
    sort: {createdAt: -1},
    limit: 10,
    skip: 0,
    select: ["email", "id"]
})

/* This will return {
    data: Array of the returned documents,
    count: the total count of user from mongolia
} */

findOne(findOptions: FindOptions)

It only finds one!

const latestUserEmail = await userRepository.findOne({
    where: {country: "Mongolia"},
    sort: {createdAt: -1},
    select: ["email", "id"]
})

count(findOptions: FindOptions)

It will return the total number of documents applies the where object.

const usersCount = await userRepository.count({
    where: {country: "Mongolia"}
})

findOneById(id: string, select)

It will find one document by its id and can select the wanted fields.

const user = await userRepository.findOneById("6309c6f839fc4980aeb34677", ["email"])

Query Caching

You can cache any query to get the results directly from the memory

repository.find({ where: { email: '[email protected]' }, cache: true }) // the default cache Timeout is 1000 MS = 1 Second

or

repository.find({ where: { email: '[email protected]' }, cache: {timeout: 3000} })

Query Builder

You can use the query builder for a better code organizing!

repo.queryBuilder()
    .where({isDeleted: false})
    .select(["email"])
    .sort({id: -1})
    .limit(10)
    .cache(true)
    .find();

Find reference Entities

Suppose we have user and CV repositories

@Entity({ name: 'cv' })
class CVEntity {
    @Allow()
    @IsRequired()
    @IsString()
    cvName: string;

    @Allow()
    @IsRequired()
    @IsString()
    currentPosition: string;
}

@Entity({ name: 'user' })
class UserEntity {
    @Allow()
    @IsEmail(undefined, { message: "The email should be valid :(" })
    @IsUnique({ isIgnoreCase: true })
    email: string;

    @Allow()
    @IsArray()
    @RefersTo({
        collection: 'cv',
        key: 'id',
        isArray: true
    })
    cvs: any[];
}

Here the user entity contains the CVs IDs (@RefersTo({collection: 'cv', .... }))

To get the all the user CVs we can easly do:

userRepository.find({select: ['cvs.cvName']})

Once we select an inner value of the CVs, that will notify the mongo-strict to get the referenced entity. We can select, match and sort by cvs.cvName or any cvs inner key

Reverse Refering

Suppose we have user and CV repositories but the CV repo is the container of the user Id.

@Entity({ name: 'user' })
class UserEntity {
    @Allow()
    @IsEmail(undefined, { message: "The email should be valid :(" })
    @IsUnique({ isIgnoreCase: true })
    email: string;
}

@Entity({ name: 'cv' })
class CVEntity {
    @Allow()
    @IsRequired()
    @IsString()
    @RefersTo({
        collection: 'user',
        key: 'id',
        reverseRefering: true,
        reverseReferingAs: 'cv'
    })
    user: string;

    @Allow()
    @IsRequired()
    @IsString()
    cvName: string;

    @Allow()
    @IsRequired()
    @IsString()
    currentPosition: string;
}

We can easly get the user of any CV by doing:

cvRepository.find({select: ['user.email', 'user.id']})

But in case if we need to get the user CVs we will need to use the Reverse Refering.

In the User entity we have nothing indicates that this user has CV/s.

Fortunutly mongo-strict supports this operation but it will not be good for the app performance.

So to be able to use that we had to add => reverseRefering: true, reverseReferingAs: 'cv' (Be carefull before doing that).

Then we can do:

userRepository.find({select: ['cv.cvName']})

The problem here that the user repository contains nothing about the CV repository so to get the user CVs the DB will have to loop through all the CV entities to get the CVs which refer to the wanted user

inserOne

mongo-strict uses a simple insertOne operation and returns the inserted document.

const insertedUser = await userRepository.insertOne({
                    email: '[email protected]',
                    name: 'mongo user',
                    country: 'mongolia'
                });

const insertedCV = await cvRepository.insertOne({
                    user: insertedUser.id,
                    cvName: 'User CV 1',
                    currentPosition: 'Developer !'
                });

You can simply insert an Object contains your entity data.

mongo-strict will validate the inserted entity and check if any uniques key are previously existing or not, check for the existence of the reference keys and all the other checks, in case of any any error it will throw an error.

We doesn't fully support the mongoDB advanced insert operations.

Update(filter: object | id: string)

To perform an update operation you need to call the Update function with the update filter or the id of the entity you want to update.

This will return 3 function you will need to pass the updated data/entity to:

1- setOne(data)

  • You need to pass just the keys you want to update in the entity.
  • Updates only one matching document in the collection that match the filter.
  • Returns the full updated Entity
const updatedUser = await userRepository.update({email: '[email protected]'}).setOne({
                    name: 'updated mongo user',
                  });

Or

const updatedUser = await userRepository.update(user.id). setOne({
                    name: 'updated mongo user',
                  });

2- setMany(data)

updates all matching documents in the collection that match the filter.

The method returns a document that contains:

  • A boolean acknowledged as true if the operation ran with write concern or false if write concern was disabled.
  • matchedCount containing the number of matched documents.
  • modifiedCount containing the number of modified documents.
  • upsertedId containing the id for the upserted document
await userRepository.update({}).setMany({
    isDeleted: false
});
// the will set isDelete to false in all users in the collection

3- replaceOne(completeEntityData)

replaceOne() replaces the first matching document in the collection that matches the filter, using the replacement document

Returns a document containing:

  • A boolean acknowledged as true if the operation ran with write concern or false if write concern was disabled.
  • matchedCount containing the number of matched documents.
  • modifiedCount containing the number of modified documents.
  • upsertedId containing the id for the upserted document.
await userRepository.update(user.id).replaceOne({
    email: '[email protected]',
    name: 'mongo user :)',
    country: 'mongolia'
});

Errors Handling

Our goal in mongo-strict is to unify the way of throwing the exceptions and give you the full control of the error message to make you able to catch the error then pass it as a response directly with no more code.

Invalid data error

For example if you marked a field as @IsEmail()

@Entity({ name: 'user' })
class UserEntity {
    @Allow()
    @IsEmail(undefined, { message: "The email should be valid :(" })
    @IsUnique({ isIgnoreCase: true, message: 'The email should be unique' })
    email: string;
}

Then you inserted an invalid email in insert or update it will throw:

try {
    // insert invalid email
} catch(e) {
    console.log(e);

    // {
    //     message: 'Invalid Data Found',
    //     invalidKeys: ['email'],
    //     errorMessages: ['The email should be valid :(']
    // }
}

Existing Unique Keys

If you marked the email as a unique key and you tried to insert an email which is already exists it will throw:

try {
    // insert already exists email
} catch(e) {
    console.log(e);

    // {
    //     message: 'Existing unique keys',
    //     existingUniqueKeys: ['email'],
    //     errorMessages: ['The email should be unique']
    // }
}

Not Found Reference Keys

If you marked any field as a reference to another collection and you inserted a not found reference it will throw:

try {
    // insert not found reference
} catch(e) {
    console.log(e);

    // {
    //     message: 'Not found reference keys',
    //     missedReferenceKeys: ['user'],
    //     errorMessages: ['The user is not found']
    // }
}

deleteOne(filter: any | id: string)

Deletes the first document that matches the filter. Use a field that is part of a unique index such as id for precise deletions.

We just call the mongoDB deleteOne, in the future we will support sql DBs onDelete...

Returns a document containing:

  • A boolean acknowledged as true if the operation ran with write concern or false if write concern was disabled.
  • deletedCount containing the number of deleted documents

deleteMany(filter: any | ids: string[])

Deletes documents one at a time. If the primary node fails during a deleteMany() operation, documents that were not yet deleted from secondary nodes are not deleted from the collection.

We just call the mongoDB deleteMany, in the future we will support sql DBs onDelete...

Returns a document containing:

  • A boolean acknowledged as true if the operation ran with write concern or false if write concern was disabled.
  • deletedCount containing the number of deleted documents

getCollection()

If you want to make any operation we don't support until now you can get the MongoDB collection and run the orginal mongo query

userRepository.getCollection().find({}).limit(10).toArray();
You might also like...

Node js package makes creating node jd dependincies files like Controllers,Entities and Repositories easier by executing a few instructions

Node js package makes creating node jd dependincies files like Controllers,Entities and Repositories easier by executing a few instructions

Nodejs Studio Node js package makes creating node js project dependincies files like Controllers,Entities and Repositories easier by executing a few i

Oct 12, 2022

Rollup + React + Babel + Prettier + Strict ESlint and Stylelint + Sass + VSCode + Playground app - Enterprise grade boilerplate

React package boilerplate by HackingBay Rollup + React 17 + Babel + Prettier + Strict ESlint and Stylelint + Sass + VSCode + Playground app - Enterpri

Jan 19, 2022

A Deno ORM for MySQL, SQLite, PostgreSQL, MongoDB, GitHub and serverless service like Deta, InspireCloud, CloudBase, LeanCloud

A Deno ORM for MySQL, SQLite, PostgreSQL, MongoDB, GitHub and serverless service like Deta, InspireCloud, CloudBase, LeanCloud.

Dec 15, 2022

Fnon is a client-side JavaScript library for models, loading indicators, notifications, and alerts which makes your web projects much better.

𝔉𝔫𝔬𝔫 Fnon is the name of my late mother, It's an Arabic word which means Art, I created this library in honor of her name. Fnon is a client-side J

Sep 11, 2022

An extension that makes Scratching much easier!

ScratchTools An extension that makes Scratching much easier! Official Discord. v1.0 ScratchTools Version 1.0 had multiple bugs, making it impossible t

May 23, 2022

A program that makes scripting videos easier.

QuickScript A program that makes scripting videos easier. Scripts can be written using only the keyboard. Installation Clone/fork the repository Insta

Jun 22, 2022

Boiler is a utility library that makes every day tasks in JavaScript easier by providing over 115 methods

Boiler is a utility library that makes every day tasks in JavaScript easier by providing over 115 methods that work on arrays, collections, functions, numbers, objects, and strings. It's the JavaScript equivalent of a Swiss Army Knife.

Nov 1, 2022

Makes Bootstrap 5.x Toasts easier to use

Makes Bootstrap 5.x Toasts easier to use

bootstrap5-toast Copied from https://github.com/Script47/Toast and updated to support Bootstrap 5.x https://getbootstrap.com/docs/5.0/components/toast

Jul 12, 2022

All in one is a CLI to make your journey in web development less painful (it makes your life way easier).

All In One CLI (Aio) The Ultimate CLI for all your needs in web development. Description This is a CLI that has all the commands you need to do anythi

Sep 25, 2022
Owner
Mohamed Kamel
Mohamed Kamel
Prisma is a next-generation object–relational mapper (ORM) that claims to help developers build faster and make fewer errors.

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Rhodin Emmanuel Nagwere 1 Oct 8, 2022
A JavaScript Library To Make Your Work Work Easier/Faster

Functionalty.js (beta) About ✍️ This Is A JavaScript Library To Make Your Work Easier/Faster, You Can See Functionalty.js Website From Here Project Cr

Ali-Eldeba 16 Aug 30, 2022
A JavaScript Library To Make Your Work Work Easier/Faster

Functionality.js (beta) About ✍️ This Is A JavaScript Library To Make Your Work Easier/Faster, You Can See Functionalty.js Website From Here Project C

Ali-Eldeba 9 May 25, 2022
A JavaScript Library To Make Your Work Work Easier/Faster

Functionality.js About ✍️ This Is A JavaScript Library To Make Your Work Easier/Faster, You Can See Functionalty.js Website From Here Project Created

functionality 16 Jun 23, 2022
An NPM package to help you get started with modern javascript tooling easier & faster

MODERNIZE.JS Creating config files for webpack and babel can be an hell of stress, this NPM package helps you get started with writing code as soon as

Kelechi Okoronkwo 5 Sep 22, 2022
🔧 Alternative to 'eval' in JavaScript that is customizable and safer!

?? better-eval An alternative to eval() in JavaScript that is customizable and safer! The eval function sucks, and there lacks alternatives that provi

Bharadwaj Duggaraju 32 Sep 14, 2022
A maybe slightly safer-ish wrapper around eval Function constructors

evalish A maybe slightly safer-ish wrapper around eval Function constructors Please maybe try something else first.. Please. evalish is a small helper

Phil Pluckthun 24 Sep 6, 2022
A maybe slightly safer-ish wrapper around eval Function constructors

evalish A maybe slightly safer-ish wrapper around eval Function constructors Please maybe try something else first.. Please. evalish is a small helper

0no.co 22 Aug 21, 2022
🎮 The only Front-End Performance Checklist that runs faster than the others

Front-End Performance Checklist ?? The only Front-End Performance Checklist that runs faster than the others. One simple rule: "Design and code with p

David Dias 15.5k Jan 1, 2023