Skip to content

hapinessjs/mongo-module

Repository files navigation

Hapiness

Mongo Module

Mongo module for the Hapiness framework including mongoose, mongoose-gridfs (deprecated) and mongoose-gridfs-bucket adapter one.

Table of contents

Using your module inside Hapiness application

yarn or npm it in your package.json

$ npm install --save @hapiness/core @hapiness/mongo rxjs

or

$ yarn add @hapiness/core @hapiness/mongo rxjs
"dependencies": {
    "@hapiness/core": "^1.3.0",
    "@hapiness/mongo": "^1.1.3",
    "rxjs": "^5.5.6",
    //...
}
//...

Importing MongoModule from the library

This module provide an Hapiness extension for Mongo. To use it, simply register it during the bootstrap step of your project like that:

import { MongoModule, MongoClientExt } from '@hapiness/mongo';

@HapinessModule({
    version: '1.0.0',
    providers: [ ],
    declarations: [ ],
    imports: [ MongoModule ]
})
class MyModule implements OnStart {

    constructor() { /* ... */ }
}

Hapiness.bootstrap(MyModule, [ MongoClientExt.setConfig(/* ... */) ]);

Back to top

Creating Adapters

The Mongo module is based on adapters. Included to the module, there is an adapter using mongoose and one using mongoose to manage gridfs.

But you can create your own adapters if you want by following some required steps described belows.

Step 1

Your adapter should be a class which inherits from AbstractHapinessMongoAdapter.

Step 2

You absolutely need to implement a static function getInterfaceName, which will return a uniq string identifier for your adapter (NOTE mongoose, mongoose-gridfs and mongoose-gridfs-bucket are already used by adapters provided by this module).

Step 3

You need to override 4 functions

    _tryConnect(): Observable<void> { /* ... */ }

    _afterConnect(): Observable<void> { /* ... */ }

    getLibrary(): any { /* ... */ }

    registerValue(): any { /* ... */ }

_tryConnect: you will create your database connection inside

Example for mongoose

protected _tryConnect(): Observable<void> {
        return Observable
            .create(observer => {
                this._isReady = false;

                if (this._db) {
                    this._db.close();
                }

                const connectOptions = {
                    server: {
                        reconnectTries: Number.MAX_VALUE,
                        reconnectInterval: 5000,
                    },
                };

                this._connection = mongoose.createConnection(this._uri, connectOptions);

                this._connection.once('connected', () => {
                    observer.next();
                    observer.complete();
                });

                this._connection.once('error', err => {
                    observer.error(err);
                });
            });
    }

_afterConnect: this function will be called just after _tryConnect if you want to manage some stuff once your connection is fine.

Example for mongoose:

protected _afterConnect(): Observable<void> {
        return Observable
            .create(observer => {
                this._db = this._connection.db;

                this.onConnected().subscribe(_ => {
                    /* ... */
                }, (e) => {
                    /* ... */
                });

                this._connection.once('error', err =>
                    this.onError(err).subscribe(_ => {
                        /* ... */
                    }, (e) => {
                        /* ... */
                    })
                );

                this._connection.once('disconnected', () =>
                    this.onDisconnected().subscribe(_ => {
                        /* ... */
                    }, (e) => {
                        /* ... */
                    })
                );

                observer.next();
                observer.complete();
            });
    }

getLibrary: this will just return the library your adapter use to use it as you want.

Example for mongoose:

public getLibrary(): any {
    return mongoose;
}

registerValue: this will register your document and return a model value to get through the DI.

Example for mongoose:

public registerValue(document, collection): any {
    return this._connection.model(collection, document);
}

NOTE DONT FORGET TO SET _isReady = true once you are done, else your adapter will never be ready.

You should also override:

    close(): Observalbe<void> { /*  */ }

Back to top

Registering adapters

When you want to create your own adapters, first you need to tell the Mongo extension to register it. The Mongo extension will add your classes and map it with the uniq identifier you put inside the static ddd.

class MyCustomAdapter extends AbstractHapinessMongoAdapter {
    public static getInterfaceName(): string {
        return 'custom-identifier-for-my-adapter';
    }

    constructor(options) { super(options); }

    /* ... */
}

Hapiness.bootstrap(
    MyModule,
    [
        MongoClientExt
            .setConfig(
                {
                    register: [ MyCustomAdapter ]
                }
            )
    ]
);

Now, the mongo extension knows that an Adapter with the identifier custom-identifier-for-my-adapter exists.

The two provided adapters don't need to be registered as it is already done.

Using a registered adapter

It will work the same for both custom adapters you made and provided adapters.

Just load them with the config you want to use:

class MyCustomAdapter extends AbstractHapinessMongoAdapter {
    public static getInterfaceName(): string {
        return 'custom-identifier-for-my-adapter';
    }

    constructor(options) { super(options); }
}

Hapiness.bootstrap(
    MyModule,
    [
        MongoClientExt
            .setConfig(
                {
                    register: [ MyCustomAdapter ]
                    load: [
                        {
                            name: 'custom-identifier-for-my-adapter',
                            config: {
                                host: 'my.hostname1.com',
                                port: 27017,
                                db: 'db_1'
                            }
                        },
                        {
                            name: 'mongoose',
                            config: {
                                host: 'my.hostname2.com',
                                port: 27017,
                                db: 'db_1'
                            }
                        }
                    ]
                }
            )
    ]
);

So you can load as many connections as you want and provide custom configs for each adapter you load.

Back to top

Configuration

When you load adapters (see previous section), you can provide a config, but you have the possibility to not provide one every time.

Lets say you want two adapters pointing to the same database, you can for that use the common option.

class MyCustomAdapter extends AbstractHapinessMongoAdapter {
    public static getInterfaceName(): string {
        return 'custom-identifier-for-my-adapter';
    }

    constructor(options) { super(options); }
}

Hapiness.bootstrap(
    MyModule,
    [
        MongoClientExt
            .setConfig(
                {
                    register: [ MyCustomAdapter ]
                    common: {
                        host: 'my.hostname.com',
                        port: 27017,
                        db: 'my_db'
                    }
                    load: [
                        {
                            name: 'custom-identifier-for-my-adapter'
                        },
                        {
                            name: 'mongoose'
                        }
                    ]
                }
            )
    ]
);

Back to top

Get your adapter anywhere

To get your adapter and play with it, you need to inject the MongoClientService in your class and call the get() function to get an instance of the adapter manager.

Once you did it, you'll be able to get your adapter with its name only or with its name and options (if you have the same adapter in different DBs or host ...) calling the function getAdapter(...).

Example showing how to get mongoose adapter for my_database:

@Injectable()
class MyModelDocument {
    private _myModelConnection: any;

    constructor(
        private _mongoClient: MongoClientService
    ) {
        this._myModelConnection = null;
    }

    init() {
        // You can do that...
        const dao = this._mongoClient.get().getAdapter('mongoose').getLibrary();

        // ... or that
        const connection = this._mongoClient.get().getAdapter('mongoose', { db: 'my_database' }).getConnection();

        const MyModel = dao.Schema({
            username: String,
        });

        this._myModelConnection = connection.model('MyModel', MyModel);
    }

    get() {
        return this._myModelConnection;
    }
}

Back to top

Model Management

You can implement and register models in the adapter

Example:

@MongoModel({
    adapter: 'mongoose',
    collection: 'collection', // The name of the collection, will be pluralize using mongoose if no collectionName is explicitly given
    collectionName: 'collectionName', // Optional, will force this name as the collection name
    options: { ... } // @see HapinessMongoAdapterConstructorArgs type
})
class MyModel extends Model {

    readonly schema;

    constructor(private mongoClientService: MongoClientService) {
        super(MyModel) // /!\ Important to get connection options
        const DAO = mongoClientService.getDao(this.connectionOptions);
        this.schema = new DAO.Schema({
            id: String
        });

        ...
    }
}

@Route({
    path: '/my-route',
    method: 'get'
})
class MyRoute implements OnGet {
    constructor(private mongoClientService: MongoClientService) {}

    onGet(request, reply) {
        const model = this.mongoClientService.getModel({ adapter: 'mongoose', options: {} }, MyModel);

        ...
    }
}

@HapinessModule({
    version: '1.0.0',
    declarations: [ MyModel, MyRoute ]
})
class MyModule {}

Back to top

Helpers functions

The module gives you an helpers to perform some basic mongo-related operations.

Just import the class from the module

import { MongoUtils } from '@hapiness/mongo'

There is 4 static functions (for now)

public static prepareUpdateObject(dto: any): any: Give to this function an object like { "meta": { "key": "value" } } and it returns you the object { "meta.key": "value" }. Very usefull to perform update operations!

public static toObjectId(id: string): Returns an ObjectID type from the given string (null if the string is not a valid ObjectID)

public static fieldsStringFromArray(fields: string[]): string: If you want to select only some fields and you need for that to compute a string from an array of string, use this function

public static filterFindCondition(condition: any): any: If you have a query object for mongo, this function will parse your condition, convert the field id into _id and then convert _id to an ObjectID before giving back the query object

Back to top

Contributing

To set up your development environment:

  1. clone the repo to your workspace,
  2. in the shell cd to the main folder,
  3. hit npm or yarn install,
  4. run npm or yarn run test.
    • It will lint the code and execute all tests.
    • The test coverage report can be viewed from ./coverage/lcov-report/index.html.

Back to top

Change History

  • v2.1.0
    • Now set by default useNewUrlParser: true, useCreateIndex: true and useFindAndModify: false on the connectionOptions of mongoose.
  • v2.0.2
    • More debug and events for mongoose adapter
  • v2.0.1
    • Fix Mongo Utils - prepareUpdateObject
  • v2.0.0
    • Updated mongoose to latest version
    • Removed the call to createConnection from the constructor of mongo adapter.
    • Now catch connection errors and pipe them out to be catch by user later on. No more waiting for timeout to realize there is a connection error.
    • MongooseGridfsAdapter is now deprecated. Use MongooseGridfsBucketAdapter
  • v1.2.0 (2018-04-05)
    • Updated packages' versions.
    • added support of OnShutdown of hapiness Core
    • added close functions to adapters.
    • Fix typos in documentation.
  • v1.1.3 (2018-01-16)
    • Fix get adapter using adapter name
  • v1.1.2 (2018-01-09)
    • Latest packages' versions.
    • Added connectionName options to identify an adapter by an alias
    • Bug fix on an indexOf !== 1 instead of indexOf !== -1
    • Documentation.
  • v1.1.1 (2017-12-12)
    • Latest packages' versions.
    • Add field collectionName to force using a specific name for a collection.
    • Fix key computation when retrieving an adapter.
    • Allow developers to not give all options for the adapter when getting a document.
    • Documentation.
  • v1.1.0 (2017-11-20)
    • Latest packages' versions.
    • Update Module + Tests related to latest core version.
    • Documentation.
    • Change packaging process.
  • v1.0.0 (2017-11-14)
    • MongoDB module implementation.
    • Tests module API.
    • Documentation.

Back to top

Maintainers

tadaweb
Julien Fauville Antoine Gomez Sébastien Ritz Nicolas Jessel Florent Bennani

Back to top

License

Copyright (c) 2017 Hapiness Licensed under the MIT license.

Back to top