NestJS is a framework that incorporates the benefits of TypeScript, IoC / DI and the Angular framework, and is rapidly evolving and gaining popularity.
Many techniques and practices are described in the official documentation . Consider writing your own Dynamic module and publishing it to npm. We use Mailchimp Transaction API as a library .
Some concepts are omitted. More details can be found in the publication Biundo the John - the Build a NestJS the Module for Knex.js .
Dynamic module
Dynamic module . , John Biundo , .
npm install @nestjsplus/dyn-schematics -g
,
nest g -c @nestjsplus/dyn-schematics dynpkg <NAME>
,
? Generate a testing client? Yes
:
| .npmignore | .prettierrc | nest-cli.json | package.json | README.md | tsconfig.build.json | tsconfig.json | tslint.json | \---src | constants.ts | index.ts | mailchimp-habr.module.ts | mailchimp-habr.providers.ts | mailchimp-habr.service.ts | main.ts | +---interfaces | index.ts | mailchimp-habr-module-async-options.interface.ts | mailchimp-habr-options-factory.interface.ts | mailchimp-habr-options.interface.ts | \---mailchimp-habr-client mailchimp-habr-client.controller.ts mailchimp-habr-client.module.ts
package.json. npm-check-updates. , , RxJS, , NestJS.
npm i --save @mailchimp/mailchimp_transactional
mailchimp . , dyn-schematics,
//mailchimp-habr-options.interface.ts
export type MailchimpHabrOptions = string;
//mailchimp-habr-options-factory.interface.ts
import { MailchimpHabrOptions } from './mailchimp-habr-options.interface';
export interface MailchimpHabrOptionsFactory {
createMailchimpHabrOptions(): | Promise<MailchimpHabrOptions> | MailchimpHabrOptions;
}
//mailchimp-habr-module-async-options.interface.ts
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
import { MailchimpHabrOptionsFactory } from './mailchimp-habr-options-factory.interface';
import { MailchimpHabrOptions } from './mailchimp-habr-options.interface';
export interface MailchimpHabrAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
inject?: any[];
useExisting?: Type<MailchimpHabrOptionsFactory>;
useClass?: Type<MailchimpHabrOptionsFactory>;
useFactory?: (...args: any[]) => Promise<MailchimpHabrOptions> | MailchimpHabrOptions;
}
Symbol MAILCHIMP_HABR_TOKEN
// constants.ts
export const MAILCHIMP_HABR_OPTIONS = Symbol('MAILCHIMP_HABR_OPTIONS');
export const MAILCHIMP_HABR_TOKEN = Symbol('MAILCHIMP_HABR_TOKEN');
mailchimp-habr.service.ts
//mailchimp-habr.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { MAILCHIMP_HABR_OPTIONS} from './constants';
import { MailchimpHabrOptions } from './interfaces';
import * as Mailchimp from '@mailchimp/mailchimp_transactional/src';
interface IMailchimpHabrService {
getInstance(): Mailchimp;
}
@Injectable()
export class MailchimpHabrService implements IMailchimpHabrService {
private serviceInstance: Mailchimp;
constructor(
@Inject(MAILCHIMP_HABR_OPTIONS) private mailchimpHabrOptions: MailchimpHabrOptions,
) {}
async getInstance(): Mailchimp {
return this.serviceInstance ? this.serviceInstance : Mailchimp(this.mailchimpHabrOptions);
}
}
, .
-, @Injectable()
, NestJS, DI. constructor , , @Inject(MAILCHIMP_HABR_OPTIONS)
Symbol, , .
, . Dynamic Module.
dyn-schematics
//mailchimp-habr.providers.ts
import { MAILCHIMP_HABR_OPTIONS } from './constants';
import { MailchimpHabrOptions } from './interfaces';
export function createMailchimpHabrProviders(options: MailchimpHabrOptions) {
return [
{
provide: MAILCHIMP_HABR_OPTIONS,
useValue: options,
},
];
}
, , , . NestJS.
NestJS Dynamic Modules . , module: MailchimpHabrModule
.
mailchimp-habr.module.ts
,
export class MailchimpHabrModule {
public static register(options: MailchimpHabrOptions): DynamicModule {
return {
module: MailchimpHabrModule,
providers: createMailchimpHabrProviders(options),
};
}
}
forRoot, NestJS.
, , , exports. , , !
, , @Module
MailchimpHabrModule
, , , ! , @Module
(forRoot/register forRootAsync/registerAsync). โโ, custom provider.
mailchimp-habr.providers.ts
// mailchimp-habr.providers.ts
export const MailchimpProvider: Provider = {
provide: MAILCHIMP_HABR_TOKEN,
useFactory: async (mailchimpService) => mailchimpService.getInstance(),
inject: [MailchimpHabrService],
};
@Module
mailchimp-habr.module.ts
@Global()
@Module({
providers: [MailchimpProvider, MailchimpHabrService],
exports: [MailchimpProvider, MailchimpHabrService],
})
export class MailchimpHabrModule {
/* */
}
! โโ, . ( ) mailchimp-habr.decorator.ts
//mailchimp-habr.decorator.ts
import { Inject } from '@nestjs/common';
import { MAILCHIMP_HABR_TOKEN } from './constants';
export const InjectMailchimp = () => Inject(MAILCHIMP_HABR_TOKEN);
! ! ? , .
import { Module } from '@nestjs/common';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller';
import { MailchimpHabrModule } from '../mailchimp-habr.module';
@Module({
controllers: [MailchimpHabrClientController],
imports: [MailchimpHabrModule.forRoot('YOUR_API_KEY')],
})
export class MailchimpHabrClientModule {}
import { Controller, Get } from '@nestjs/common';
import { InjectMailchimp } from '../mailchimp-habr.decorator';
@Controller()
export class MailchimpHabrClientController {
constructor(@InjectMailchimp() private readonly mailchimpHabrService) {}
@Get()
index() {
return this.mailchimpHabrService.users.ping();
}
}
.
npm run start:dev
http://localhost:3000/ ( curl, postman, insomnia...).
ยซPONG!ยป, api key. , , this.mailchimpHabrService.users.ping();
? ! ! forRoot/register forRootAsync/registerAsync.
import { Module } from '@nestjs/common';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller';
import { MailchimpHabrModule } from '../mailchimp-habr.module';
@Module({
controllers: [MailchimpHabrClientController],
imports: [MailchimpHabrModule.forRootAsync({ useFactory: () => 'API_KEY' })],
})
export class MailchimpHabrClientModule {}
... !
, useFactory? NestJS โ ConfigModule.
imports
inject
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MailchimpHabrModule } from '../mailchimp-habr.module';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller';
@Module({
controllers: [MailchimpHabrClientController],
imports: [
ConfigModule.forRoot(),
MailchimpHabrModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (config: ConfigService) => {
return config.get('API_KEY');
},
inject: [ConfigService],
})],
})
export class MailchimpHabrClientModule {}
Happened? I think no. Most likely you got the error:
Something is wrong here. Following the documentation, we look for an error ... Looking closely at forRootAsync, we find that we do not handle imports in options in any way. Let's add.
export class MailchimpHabrModule {
public static forRootAsync(options: MailchimpHabrAsyncOptions,): DynamicModule {
return {
module: MailchimpHabrModule,
imports: options.imports ?? [], // imports
providers: [
...this.createProviders(options),
],
};
}
}
Let's try again. Everything is working!
Unit testing
NestJS supports Jest out of the box. Let's take this opportunity. Let's create a filemailchimp-habr.spec.ts
import * as Mailchimp from '@mailchimp/mailchimp_transactional/src';
import { Test } from '@nestjs/testing';
import { MAILCHIMP_HABR_TOKEN, MailchimpHabrModule } from './';
describe('Mailchimp forRoot', () => {
let mailchimpService: Mailchimp;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [MailchimpHabrModule.forRoot('MAILCHIPM_API_KEY')],
}).compile();
mailchimpService = moduleRef.get<Mailchimp>(MAILCHIMP_HABR_TOKEN);
});
describe('mailchimpHabrService', () => {
it('method ping exists', async () => {
expect(mailchimpService.users.ping).toBeDefined();
});
it('method is a function', async () => {
expect(mailchimpService.users.ping).toBeInstanceOf(Function);
});
it('users.ping() should return "PONG"', async () => {
const result = 'PONG';
jest
.spyOn(mailchimpService.users, 'ping')
.mockImplementation(() => result);
expect(await mailchimpService.users.ping()).toBe(result);
});
});
});
All tests pass. Similarly, we test registration viaforRootAsync().
Outcome
Dynamic modules NestJS allows you to wrap a lot of libraries! Writing your own modules allows you to shorten the code in your projects. And publishing them to npm makes life easier not only for you.
The module is available on npm and GitHub .