NestJS using Environment Variable - node.js

I have my environment variable in the root of the director and the following config files to import as a module.
/config/config.service.ts
// NPM Packages
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as Joi from '#hapi/joi';
export type EnvConfig = Record<string, string>;
export class ConfigService {
private readonly envConfig: EnvConfig;
constructor(filePath: string) {
const config = dotenv.parse(fs.readFileSync(filePath));
this.envConfig = this.validateInput(config);
}
/**
* Ensures all needed variables are set,
* and returns the validated JavaScript object
* including the applied default values.
*/
private validateInput(envConfig: EnvConfig): EnvConfig {
const envVarsSchema: Joi.ObjectSchema = Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
MONGO_URI: Joi.required(),
API_AUTH_ENABLED: Joi.boolean().required(),
IS_AUTH_ENABLED: Joi.boolean().required(),
JWT_SECRET: Joi.required(),
JWT_EXPIRE: Joi.required(),
JWT_COOKIE_EXPIRE: Joi.required(),
});
const { error, value: validatedEnvConfig } = envVarsSchema.validate(
envConfig,
);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
return validatedEnvConfig;
}
get(key: string): string {
return this.envConfig[key];
}
}
/config/config.module.ts
// Core Packages
import { Module } from '#nestjs/common';
// Custom Packages
import { ConfigService } from './config.service';
#Module({
providers: [
{
provide: ConfigService,
useValue: new ConfigService(
`${process.env.NODE_ENV || 'development'}.env`,
),
},
],
exports: [ConfigService],
})
export class ConfigModule {}
I have imported it globally in app.modules as follows
#Module({
imports: [ConfigModule]
})
In my database provider src/database/database.providers.ts I am trying to import the MONGO_URL variable but am getting an error.
// NPM Packages
import * as mongoose from 'mongoose';
// Custom Packages
import { envConfig } from '../config/config.service';
const mongoUrl: string = envConfig.get('MONGO_URI');
export const databaseProviders = [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (): Promise<typeof mongoose> =>
await mongoose.connect(mongoUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
}),
},
];
Error:
Property 'get' does not exist on type 'typeof ConfigService'.ts(2339)

Here, you are importing the ConfigService class, not an instance of the ConfigService, so there is no static get method on the ConfigService class, just an instance method. What you can do instead is modify your databaseProvider to look like this:
export const databaseProviders = [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (private readonly configService: ConfigService): Promise<typeof mongoose> =>
await mongoose.connect(configSerivce.get('MONGO_URI'), {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
}),
inject: [ConfigService],
imports: [ConfigModule]
},
];

Related

nestjs use service from external module inside a dynamic module

AppModule
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: !!ENV ? `.env.${ENV}` : '.env',
}),
AedesModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
port: 1883,
}),
inject: [ConfigService],
}),
AuthModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
My AEDES MODULE:
import { DynamicModule, Global, Module, Provider } from '#nestjs/common';
import { AedesModuleAsyncOptions, AedesModuleOptions } from './#types/package';
import { AedesService } from './aedes.service';
#Global()
#Module({
providers: [AedesService],
exports: [AedesService],
})
export class AedesModule {
public static forRootAsync(options: AedesModuleAsyncOptions): DynamicModule {
const customOptions = this.getCustomOptions(options);
return {
module: AedesModule,
imports: options.imports || [],
providers: [customOptions, this.aedesProvider],
exports: [customOptions, this.aedesProvider],
};
}
private static getCustomOptions(options: AedesModuleAsyncOptions): Provider {
return {
provide: 'AEDES_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
};
}
private static aedesProvider: Provider = {
provide: AedesService,
useFactory: async (options: AedesModuleOptions) => {
const aedes = new AedesService(options);
aedes.init();
return aedes;
},
inject: ['AEDES_OPTIONS'],
};
private static getOptionsProvider(options: AedesModuleOptions): Provider {
return {
provide: 'AEDES_OPTIONS',
useValue: options,
};
}
}
type definitions:
import { ModuleMetadata } from '#nestjs/common';
export interface AedesModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
inject?: any[];
useFactory: (
...args: any[]
) => Promise<AedesModuleOptions> | AedesModuleOptions;
}
export interface AedesModuleOptions {
port: number;
}
My Service:
export class AedesService {
public broker: aedes;
private port: number;
constructor(options: AedesModuleOptions) {
this.port = options.port;
}
init() {
this.broker = new aedes({
authenticate: async (client, username, password, done) => {
console.log(username, password);
const decoded: any =
await this.authService.manualAccessTokenVerification(
password.toString(),
); // I can't inject or import the existing authService which importing from another module.
console.log(decoded);
return done(null, true);
},
});
const mqttServer = createServer(this.broker);
mqttServer.listen(this.port, () => {
Logger.log(`MQTT server listening on ${this.port}`);
});
}
}
I can't inject or import the existing authService which importing from another module. Is it possible?

get mongoConnection on beforeAll and AfterAll

I want to get the MongoConnection before my tests and remove all collections after the test.
I create a file like test-setup.js:
const mongoose = require('mongoose')
mongoose.promise = global.Promise
async function removeAllCollections () {
const collections = Object.keys(mongoose.connection.collections)
for (const collectionName of collections) {
const collection = mongoose.connection.collections[collectionName]
await collection.deleteMany()
}
}
async function dropAllCollections () {
const collections = Object.keys(mongoose.connection.collections)
for (const collectionName of collections) {
const collection = mongoose.connection.collections[collectionName]
console.log(collectionName)
try {
await collection.drop()
} catch (error) {
// Sometimes this error happens, but you can safely ignore it
if (error.message === 'ns not found') return
// This error occurs when you use it.todo. You can
// safely ignore this error too
if (error.message.includes('a background operation is currently running')) return
console.log(error.message)
}
}
}
module.exports = {
setupDB (databaseName, runSaveMiddleware = false) {
// Connect to Mongoose
beforeAll(async () => {
const url = 'mongodb://127.0.0.1:27017/myDb'
let conn = await mongoose.connect(url, { useNewUrlParser: true })
console.log(conn.collections)
})
// Seeds database before each test
// beforeEach(async () => {
// await seedDatabase(runSaveMiddleware)
// })
// Cleans up database between each test
afterEach(async () => {
await removeAllCollections()
})
// Disconnect Mongoose
afterAll(async () => {
await dropAllCollections()
await mongoose.connection.close()
})
}
}
At the moment the seedDatabase is commented, but I'd like to create a database per each e2e files, but it's a second step.
So, I add this file to jest-e2e.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"setupFilesAfterEnv": ["<rootDir>/test-setup.js"]
}
And on my test:
import * as request from 'supertest';
import {
APP_URL,
TESTER_EMAIL,
TESTER_PASSWORD,
MAIL_HOST,
MAIL_PORT,
} from '../utils/constants';
describe('Auth user (e2e)', () => {
const app = APP_URL;
const mail = `http://${MAIL_HOST}:${MAIL_PORT}`;
const newUserFirstName = `Tester${Date.now()}`;
const newUsername = `E2E.${Date.now()}`;
const newUserEmail = `User.${Date.now()}#example.com`;
const newUserPassword = `secret`;
const { setupDB } = require('../test-setup.js')
setupDB('users', true)
it('Login: /api/v1/auth/email/login (POST)', () => {
return request(app)
.post('/api/v1/auth/email/login')
.send({ email: TESTER_EMAIL, password: TESTER_PASSWORD })
.expect(404)
.expect(({ body }) => {
expect(404);
expect(body.error).toBe('Not Found');
});
});
it('Register new user: /api/v1/auth/email/register (POST)', async () => {
return request(app)
.post('/api/v1/auth/email/register')
.send({
email: newUserEmail,
password: newUserPassword,
name: newUserFirstName,
username: newUsername,
})
.expect(201);
});
The test works, but still using the "main" database, the same database that I can find on app.module
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
uri: configService.get('database.url'),
}),
inject: [ConfigService],
}),
How can use the new database per each tests and delete the collections after the tests?
If I put console.log on my test-setup.js I can see the output.
UPDATE:
app.module.ts
import { Module } from '#nestjs/common';
import { ConfigModule, ConfigService } from '#nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { MongooseModule } from '#nestjs/mongoose';
import { AuthModule } from './auth/auth.module';
import { MailModule } from './mail/mail.module';
import { I18nModule } from 'nestjs-i18n/dist/i18n.module';
import { I18nJsonParser } from 'nestjs-i18n/dist/parsers/i18n.json.parser';
import { HeaderResolver } from 'nestjs-i18n';
import { MailerModule } from '#nestjs-modules/mailer';
import { MailConfigService } from './mail/mail-config.service';
import databaseConfig from './config/database.config';
import authConfig from './config/auth.config';
import appConfig from './config/app.config';
import mailConfig from './config/mail.config';
import * as path from 'path';
import { ForgotModule } from './forgot/forgot.module';
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env', '.env.dev', '.env.stage', '.env.prod'], //if a variable is found in multiple files, the first one takes precedence.
load: [
databaseConfig,
authConfig,
appConfig,
mailConfig,
],
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
uri: configService.get('database.url'),
// useNewUrlParser: true,
// useFindAndModify: false,
// useCreateIndex: true,
}),
inject: [ConfigService],
}),
MailerModule.forRootAsync({
useClass: MailConfigService,
}),
I18nModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
fallbackLanguage: configService.get('app.fallbackLanguage'),
parserOptions: {
path: path.join(
configService.get('app.workingDirectory'),
'src',
'i18n',
'translations',
),
},
}),
parser: I18nJsonParser,
inject: [ConfigService],
resolvers: [new HeaderResolver(['x-custom-lang'])],
}),
AuthModule,
UsersModule,
ForgotModule,
MailModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Nestjs graphql upload error: POST body missing. Did you forget use body-parser middleware?

I'm using NestJS 7 with graphql on node v14x
This is my graphql module configuration
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '#nestjs/common';
import { graphqlUploadExpress } from "graphql-upload"
import { GraphQLModule } from '#nestjs/graphql';
import { ConfigService } from '#nestjs/config';
import { ApolloServerDataLoaderPlugin } from '#webundsoehne/nestjs-graphql-typeorm-dataloader';
import { GraphqlConfig } from './#core/config/config.interface';
import { getConnection } from 'typeorm';
#Module({
imports: [
GraphQLModule.forRootAsync({
useFactory: async (configService: ConfigService) => {
const graphqlConfig = configService.get<GraphqlConfig>('graphql');
return {
buildSchemaOptions: {
numberScalarMode: 'integer',
plugins: [new ApolloServerDataLoaderPlugin({ typeormGetConnection: getConnection })]
},
sortSchema: graphqlConfig.sortSchema,
autoSchemaFile:
graphqlConfig.schemaDestination || './src/schema.graphql',
debug: graphqlConfig.debug,
path: graphqlConfig.endPoint,
uploads: false,
playground: graphqlConfig.playgroundEnabled,
context: ({ req, connection }) => {
if (connection) {
return { req: { headers: connection.context } }
}
return { req }
},
installSubscriptionHandlers: true,
dateScalarMode: "isoDate",
subscriptions: {
keepAlive: 5000
}
};
},
inject: [ConfigService],
}),
],
})
export class GQLModule implements NestModule {
constructor(private readonly configService: ConfigService) { }
configure(consumer: MiddlewareConsumer) {
const graphqlConfig = this.configService.get<GraphqlConfig>('graphql');
consumer.apply(graphqlUploadExpress()).forRoutes(graphqlConfig.endPoint)
}
}
After getting stuck on file upload not working on node v14.x, I find this issue comment. And I'm importing everything from graphql-upload on my resolver, But still getting error message like POST body missing. Did you forget use body-parser middleware?
Can anybody help me on this?
I have nearly identical configuration, and it works correctly for me. Since I use both REST and GraphQL, I have limited the route to 'graphql'
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { ServeStaticModule } from '#nestjs/serve-static';
import { MongooseModule } from '#nestjs/mongoose';
import { GraphQLModule } from '#nestjs/graphql';
import { AuthorModule } from '../author/author.module';
import { ApiService } from './api.service';
import { join } from 'path';
const {
graphqlUploadExpress,
} = require('graphql-upload');
const isLocalEnvironment = () => process.env.ENV === 'local';
console.log('isLocal', isLocalEnvironment(), process.env.ENV);
const modules = [
AuthorModule,
GraphQLModule.forRoot({
playground: isLocalEnvironment(),
fieldResolverEnhancers: ['guards'],
debug: isLocalEnvironment(),
autoSchemaFile: isLocalEnvironment() ? 'schema.gql' : true,
introspection: isLocalEnvironment(),
useGlobalPrefix: true,
context: ({ req }) => ({ req }),
}),
MongooseModule.forRoot(process.env.COSMOSDB_CONNECTION_STRING, {
useNewUrlParser: true,
useUnifiedTopology: true,
retryWrites: false,
autoIndex: true,
}),
];
#Module({
imports: modules,
providers: [ApiService],
})
export class APIModule implements NestModule{
configure(consumer: MiddlewareConsumer) {
consumer.apply(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 })).forRoutes('graphql');
}
}
In my main.ts file, I bootstrap the application.
if (process.env.NODE_ENV !== 'production') require('dotenv').config();
import { NestFactory } from '#nestjs/core';
import { APIModule } from './api/api.module';
import { INestApplication } from '#nestjs/common';
import { json, urlencoded } from 'body-parser';
import 'isomorphic-fetch';
const prefix = "api/";
async function bootstrap() {
const port = process.env.PORT;
const app: INestApplication = await NestFactory.create(APIModule);
app.enableCors();
app.use(json({ limit: '50mb' }));
app.use(urlencoded({ limit: '50mb', extended: true }));
// Because I use the global prefix of api/, I need to hit my service at /api/graphql
app.setGlobalPrefix(prefix);
console.log('Starting on port ' + port);
await app.listen(port);
return app;
}
bootstrap();
Since your configuration looks good to me, I'm guessing something went wrong in your bootstrap function, or that you aren't hitting the /graphql route.
I am using the latest version of NestJS which no longer includes a graphql upload implementation, so unlike you, I do not need to turn uploads off.

Nest JS - Return undefined when trying to get user context in RolesGuard

I am trying to add role guard in Nest JS API. I used Passport, Jwt authentication for this.
In my RolesGuard class I made the request and get the user from it to check the user role valid or not. I attached the code below.
roles.guard.ts
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user: User = request.user;
return this.userService.findOne(user.id).pipe(
map((user: User) => {
const hasRole = () => roles.indexOf(user.role) > -1;
let hasPermission: boolean = false;
if (hasRole()) {
hasPermission = true;
}
return user && hasPermission;
}),
);
}
Problem here is context.switchToHttp().getRequest() returns object, which is undefined. So I could not get user details from it.
After I had some research about this error I found that order of the decorators in controller can be the issue. Then I changed the order, but still problem appears as same. Bellow I added that code also.
user.controller.ts
#UseGuards(JwtAuthGuard, RolesGuard)
#hasRoles(UserRole.USER)
#Get()
findAll(): Observable<User[]> {
return this.userService.findAll();
}
-Thank you-
if you are using graphql you can make the changes to fit the code below
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { GqlExecutionContext } from '#nestjs/graphql';
import { Role } from 'src/enums/role.enum';
import { ROLES_KEY } from '../auth';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const ctx = GqlExecutionContext.create(context);
const user = ctx.getContext().req.user;
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
if you are not using graphql, make the changes to fit the code below
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
then in you controller you can do
#UseGuards(JwtAuthGuard, RolesGuard)
#hasRoles(UserRole.USER)
#Get()
findAll(): Observable<User[]> {
return this.userService.findAll();
}
finally, for all this to work add the global guard to your app.module.ts
like so
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import {MongooseModule} from '#nestjs/mongoose';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import configuration from 'src/config/configuration';
import { RolesGuard } from 'src/auth/auth';
import { UsersModule } from '../users/users.module';
import { AdminsModule } from 'src/admin/admins.module';
import { config } from 'dotenv';
import * as Joi from 'joi';
config();
#Module({
imports: [
// other modules
UsersModule,
// configuration module
ConfigModule.forRoot({
isGlobal: true,
cache: true,
load: [configuration],
expandVariables: true,
// validate stuff with Joi
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(5000),
}),
validationOptions: {
// allow unknown keys (change to false to fail on unknown keys)
allowUnknown: true,
abortEarly: true,
},
}),
// connect to mongodb database
MongooseModule.forRoot(process.env.DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
],
controllers: [AppController],
providers: [
AppService,
{
provide: 'APP_GUARD',
useClass: RolesGuard,
}
],
})
export class AppModule {}

How to change a Database connection dynamically with Request Scope Providers in Nestjs?

Working on a project with Nestjs 6.x, Mongoose, Mongo, etc...
Regarding to the Back End, in my use case, I must change the connection of one of my databases depending of some conditions/parameters coming from some requests.
Basically, I have this
mongoose.createConnection('mongodb://127.0.0.1/whatever-a', { useNewUrlParser: true })
and I want to change to, for example
mongoose.createConnection('mongodb://127.0.0.1/whatever-b', { useNewUrlParser: true })
Therefore, I have in Nestjs the first provider
export const databaseProviders = [
{
provide: 'DbConnectionToken',
useFactory: async (): Promise<typeof mongoose> =>
await mongoose.createConnection('mongodb://127.0.0.1/whatever', { useNewUrlParser: true })
}
I was researching for a while and I found out that in release Nestjs 6.x there are provider requests allowing me to modify dynamically Per-request the injection of some providers.
Anyway, I don't know how to achieve my change neither if it is going to be working in case I'd achieve that
Can anyone help or guide me?
Many thanks in advance.
You can do the following using Nest's built-in Mongoose package:
/*************************
* mognoose.service.ts
*************************/
import { Inject, Injectable, Scope } from '#nestjs/common';
import { MongooseOptionsFactory, MongooseModuleOptions } from '#nestjs/mongoose';
import { REQUEST } from '#nestjs/core';
import { Request } from '#nestjs/common';
#Injectable({ scope: Scope.REQUEST })
export class MongooseConfigService implements MongooseOptionsFactory {
constructor(
#Inject(REQUEST) private readonly request: Request,) {
}
createMongooseOptions(): MongooseModuleOptions {
return {
uri: request.params.uri, // Change this to whatever you want; you have full access to the request object.
};
}
}
/*************************
* mongoose.module.ts
*************************/
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { MongooseConfigService } from 'mognoose.service';
#Module({
imports: [
MongooseModule.forRootAsync({
useClass: MongooseConfigService,
}),
]
})
export class DbModule {}
Then, you can attach whatever you want to the request and change the database per request; hence the use of the Scope.REQUEST. You can read more about Injection Scopes on their docs.
Edit: If you run into issues with PassportJS (or any other package) or the request is empty, it seems to be an error that relates to PassportJS (or the other package) not supporting request scopes; you may read more about the issue on GitHub regarding PassportJS.
I did a simple implementation for nest-mongodb,
The main changes are in mongo-core.module.ts where I store the connections in a map and used them if available instead of creating a new connection every time.
import {
Module,
Inject,
Global,
DynamicModule,
Provider,
OnModuleDestroy,
} from '#nestjs/common';
import { ModuleRef } from '#nestjs/core';
import { MongoClient, MongoClientOptions } from 'mongodb';
import {
DEFAULT_MONGO_CLIENT_OPTIONS,
MONGO_MODULE_OPTIONS,
DEFAULT_MONGO_CONTAINER_NAME,
MONGO_CONTAINER_NAME,
} from './mongo.constants';
import {
MongoModuleAsyncOptions,
MongoOptionsFactory,
MongoModuleOptions,
} from './interfaces';
import { getClientToken, getContainerToken, getDbToken } from './mongo.util';
import * as hash from 'object-hash';
#Global()
#Module({})
export class MongoCoreModule implements OnModuleDestroy {
constructor(
#Inject(MONGO_CONTAINER_NAME) private readonly containerName: string,
private readonly moduleRef: ModuleRef,
) {}
static forRoot(
uri: string,
dbName: string,
clientOptions: MongoClientOptions = DEFAULT_MONGO_CLIENT_OPTIONS,
containerName: string = DEFAULT_MONGO_CONTAINER_NAME,
): DynamicModule {
const containerNameProvider = {
provide: MONGO_CONTAINER_NAME,
useValue: containerName,
};
const connectionContainerProvider = {
provide: getContainerToken(containerName),
useFactory: () => new Map<any, MongoClient>(),
};
const clientProvider = {
provide: getClientToken(containerName),
useFactory: async (connections: Map<any, MongoClient>) => {
const key = hash.sha1({
uri: uri,
clientOptions: clientOptions,
});
if (connections.has(key)) {
return connections.get(key);
}
const client = new MongoClient(uri, clientOptions);
connections.set(key, client);
return await client.connect();
},
inject: [getContainerToken(containerName)],
};
const dbProvider = {
provide: getDbToken(containerName),
useFactory: (client: MongoClient) => client.db(dbName),
inject: [getClientToken(containerName)],
};
return {
module: MongoCoreModule,
providers: [
containerNameProvider,
connectionContainerProvider,
clientProvider,
dbProvider,
],
exports: [clientProvider, dbProvider],
};
}
static forRootAsync(options: MongoModuleAsyncOptions): DynamicModule {
const mongoContainerName =
options.containerName || DEFAULT_MONGO_CONTAINER_NAME;
const containerNameProvider = {
provide: MONGO_CONTAINER_NAME,
useValue: mongoContainerName,
};
const connectionContainerProvider = {
provide: getContainerToken(mongoContainerName),
useFactory: () => new Map<any, MongoClient>(),
};
const clientProvider = {
provide: getClientToken(mongoContainerName),
useFactory: async (
connections: Map<any, MongoClient>,
mongoModuleOptions: MongoModuleOptions,
) => {
const { uri, clientOptions } = mongoModuleOptions;
const key = hash.sha1({
uri: uri,
clientOptions: clientOptions,
});
if (connections.has(key)) {
return connections.get(key);
}
const client = new MongoClient(
uri,
clientOptions || DEFAULT_MONGO_CLIENT_OPTIONS,
);
connections.set(key, client);
return await client.connect();
},
inject: [getContainerToken(mongoContainerName), MONGO_MODULE_OPTIONS],
};
const dbProvider = {
provide: getDbToken(mongoContainerName),
useFactory: (
mongoModuleOptions: MongoModuleOptions,
client: MongoClient,
) => client.db(mongoModuleOptions.dbName),
inject: [MONGO_MODULE_OPTIONS, getClientToken(mongoContainerName)],
};
const asyncProviders = this.createAsyncProviders(options);
return {
module: MongoCoreModule,
imports: options.imports,
providers: [
...asyncProviders,
clientProvider,
dbProvider,
containerNameProvider,
connectionContainerProvider,
],
exports: [clientProvider, dbProvider],
};
}
async onModuleDestroy() {
const clientsMap: Map<any, MongoClient> = this.moduleRef.get<
Map<any, MongoClient>
>(getContainerToken(this.containerName));
if (clientsMap) {
await Promise.all(
[...clientsMap.values()].map(connection => connection.close()),
);
}
}
private static createAsyncProviders(
options: MongoModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
} else if (options.useClass) {
return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
} else {
return [];
}
}
private static createAsyncOptionsProvider(
options: MongoModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
} else if (options.useExisting) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: async (optionsFactory: MongoOptionsFactory) =>
await optionsFactory.createMongoOptions(),
inject: [options.useExisting],
};
} else if (options.useClass) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: async (optionsFactory: MongoOptionsFactory) =>
await optionsFactory.createMongoOptions(),
inject: [options.useClass],
};
} else {
throw new Error('Invalid MongoModule options');
}
}
}
Check out the full implementation

Resources