@webundsoehne/nestjs-util v7.1.7
@webundsoehne/nestjs-util
Description
This is a collection of useful modules on creating a Nest project. Mostly all of this modules are used by the in-house boilerplate of Web & Söhne.
Publishing
This repository now uses conventional commits since it's monorepo structure. On matching commits that is related to this package, the source-code will be automatically transpiled and published to NPM.
Modules
- Internal
- Maintenance
- Info-Header
- Config
- Logger
- Swagger
- Microservice-Client Provider
- Decorators
- Pipes
- Exception-Filters
- Cache-Lifetime
- Request-Profiler
- Stay in touch
Internal
This is a NestJS controller module for internal API endpoints, which can simply be added to each project.
The controller provides you 2 endpoints /status
and /changelog
.
Usage
import { InternalModule } from '@webundsoehne/nestjs-util'
@Module({
imports: [ InternalModule ]
})
class ServerModule implements NestModule {
async configure (): Promise<any> {
await setEnvironmentVariables()
}
}
Configuration
Key | Type | Default | Description |
---|---|---|---|
misc.changelogFile | String | 'CHANGELOG.md' | The filepath of the project's changelog information |
misc.lastUpdateFile | String | '.last-update' | The filepath of the projects's last update file |
Status
The status endpoint returns the current API version set during the process environment and the last modification of the .last-update
in your root directory.
The version will be set with the util function setEnvironmentVariables()
read from package.json
and the file will normally be generated/modified during the deployment process.
You may change this value with the misc.lastUpdateFile
configuration.
Changelog
This endpoint simply reads and response the CHANGELOG.md
from your root directory.
You can change the filepath with the configuration value misc.changelogFile
.
Maintenance
The maintenance module gives you the possibility to generete and remove a lock file, as well as checking if the lock file exists and throwing a preconfigured ServiceUnavailableException
error.
You may use the maintenance module anywhere in your project, e.g. for database migrations.
Usage
import { MaintenanceMiddleware, MaintenanceModule } from '@webundsoehne/nestjs-util'
@Module({
imports: [ MaintenanceModule ]
})
class ServerModule implements NestModule {
async configure (consumer: MiddlewareConsumer): Promise<any> {
consumer
.apply(MaintenanceMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL })
}
}
Methods
Name | Return | Description |
---|---|---|
enable | void | Create the configured lock file |
disable | void | Remove the generated lock file |
isEnabled | Boolean | Check if there is already a lock file |
throwException | void | Throw the preconfigured exception |
Configuration
If there is no
misc.maintenanceNotification
set, it will be generated with following template string: `${url.basePath} is currently down for maintenance`
Key | Type | Default | Description |
---|---|---|---|
url.basePath | String | The base API url of your project | |
misc.maintenanceNotification | String | (see hint above) | The notification, which will be thrown in case of maintenance |
misc.lockfile | String | 'maintenance.lock' | The filepath of the projects's maintenance lock file |
Locker
The maintenance locker can be used combined with the nest-schedule
module for putting the API into maintenance mode programmatically while a background task executes.
Usage
import { MaintenanceLocker } from '@webundsoehne/nestjs-util'
import { NestSchedule, Timeout, UseLocker } from 'nest-schedule'
@Injectable()
export class BgTask extends NestSchedule {
@Timeout(0, {})
@UseLocker(MaintenanceLocker)
async bgTask (): Promise<void> {}
}
Middleware
The middleware of the maintenance module, uses directly the MaintenanceService
, to check if there exists a lock file and raises the correct exception. You will see the implementation in the usage block above.
Info-Header
The information header middleware is a really short NestMiddleware
which set the X-Api-Name
and X-Api-Version
response header out of the process.env
data.
Both environment variables will be set with the setEnvironmentVariables
util function, which loads the information from the package.json
.
Usage
import { SetApiInfoHeaderMiddleware, setEnvironmentVariables} from '@webundsoehne/nestjs-util'
class ServerModule implements NestModule {
async configure (consumer: MiddlewareConsumer): Promise<any> {
await setEnvironmentVariables()
consumer
.apply(SetApiInfoHeaderMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL })
}
}
Config
This is a NestJS service, which allows you to uses the great config library with decorators. But for scripts or in common no classes, you can use it normally too.
Important
The
@Configurable()
has to be the last decorator before the function is initiated.
Usage
import { ConfigParam, ConfigService, Configurable, InjectConfig } from '@webundsoehne/nestjs-util'
// 1. the old (static) way
const configValue1 = ConfigService.get('value')
class CustomService {
// 2. inject the whole config service, normally you wont use this ways
constructor ( @InjectConfig() private readonly config: ConfigService) {
const configValue2 = this.config.get('value')
}
// 3. load the config directly as function parameter
@AnythingElse()
@Configurable()
testConfig (@ConfigParam('value', 'default-value') value3?: string) {
...
}
}
Logger
Customized logger service, which uses winston for nicer output.
Usage
Set an instance of the logger to the application during creation, as part of the NestApplicationOptions
.
import { LoggerService } from '@webundsoehne/nestjs-util'
const app = await NestFactory.create<INestApplication>(ServerModule, new FastifyAdapter(), {
logger: new LoggerService()
})
After the logger got set to the application, all NestJS logging output, will be handled by our customized logger.
import { Logger } from '@nestjs/common'
class CustomService {
private readonly logger: Logger = new Logger(this.constructor.name)
private readonly loggerWithoutContext: Logger = new Logger()
constructor () {
this.logger.verbose('log message')
// [2020-01-01T12:00:00.000Z] [verbose] [CustomService] - log message
this.logger.verbose('log message, with custom context', 'ForcedContext')
// [2020-01-01T12:00:00.000Z] [verbose] [ForcedContext] - log message, with custom context
this.logger.loggerWithoutContext('log message')
// [2020-01-01T12:00:00.000Z] [verbose] [LoggerService] - log message
this.logger.loggerWithoutContext('log message, with custom context', 'ForcedContext')
// [2020-01-01T12:00:00.000Z] [verbose] [ForcedContext] - log message, with custom context
}
}
Swagger
Automatically creates a Swagger documentation out of your controllers. For detailed information about the how-to, take a deeper look at the Nest documentation.
Usage
import { SwaggerService } from '@webundsoehne/nestjs-util'
const app = await NestFactory.create<INestApplication>(ServerModule, new FastifyAdapter())
SwaggerService.enable(app, options)
Parameters
Name | Required | Type | Description |
---|---|---|---|
app | true | INestApplication | The Nest application on which the documentation should be created |
options | false | SwaggerOptions | Options for customize the default created document |
config | false | SwaggerConfig | Standard configuration, which are required for creating a documentation |
Types
SwaggerOptions
Name | Required | Type | Description |
---|---|---|---|
customize | false | Function | This function takes an DocumentBuilder modifies and returns it |
SwaggerConfig
Name | Required | Type | Description |
---|---|---|---|
useHttps | true | Boolean | If the API is behind SSL encryption |
basePath | true | String | The base-path of the API |
path | true | String | The sub-path to reach the API |
title | true | String | The name of the API or the customer |
description | true | String | A description for the whole API |
Microservice-Client Provider
Microservice client provider is a way to provide multiple microservice clients globally as well as accesing them through one common service with auto-typing to make things more convienent.
Currently only supports RabbitMQ out-of-the-box.
Usage
Create your own types for message queue names, patterns and request-response maps in a common-package that is accesible for every service in monorepo.
- Define queue names.
// microservice-provider.constants.ts export enum MessageQueues { MOCK_QUEUE = 'MOCK_QUEUE' }
- Define message patterns for given queue.
// patterns/some-queue.pattern.ts export enum MockPattern { MOCK_DEFAULT = 'mock' }
- Define message request-response maps for given message patterns.
// interfaces/some-queue.interface.ts import { MockPattern } from '../patterns' import { MicroserviceProviderBaseMessage, BaseMessageIndexes } from '@webundsoehne/nestjs-util/dist/microservices' // we need this base message indexes because of typescript indexing enum problem. export declare class MockMessage extends BaseMessageIndexes implements MicroserviceProviderBaseMessage<AppPattern> { [MockPattern.MOCK_DEFAULT]: { response: any | never request: any | never } }
- Put the message patterns in to maps to match the patterns and request-responses to queues.
// microservice-provider.constants.ts import { MockMessage } from './interfaces' import { MockPattern } from './patterns' import { BaseMessageQueueMap, BaseMessageQueuePatterns } from '@webundsoehne/nestjs-util/dist/microservices' export declare class MessageQueuePatterns implements BaseMessageQueuePatterns<MessageQueues> { [MessageQueues.MOCK_QUEUE]: MockPattern } export declare class MessageQueueMap implements BaseMessageQueueMap<MessageQueues> { [MessageQueues.MOCK_QUEUE]: MockMessage }
- Create your helper types for convienence from generics to not fill out the generics everytime.
// microservice-provider.interface.ts import { MessageQueues, MessageQueuePatterns, MessageQueueMap } from './microservice-provider.constants' import { MicroserviceProviderService } from '@webundsoehne/nestjs-util/dist/microservices' /** * Helper type for microservice client. */ export type MicroserviceClient = MicroserviceProviderService<MessageQueues, MessageQueuePatterns, MessageQueueMap> /** * Helper type for microservice requests. */ export type MicroserviceRequest< Queue extends MessageQueues, Pattern extends MessageQueuePatterns[Queue] > = MessageQueueMap[Queue][Pattern]['request'] /** * Helper type for microservice responses. */ export type MicroserviceResponse< Queue extends MessageQueues, Pattern extends MessageQueuePatterns[Queue] > = MessageQueueMap[Queue][Pattern]['response']
- You can utilize these helper types in two ways in your microservice-server or directly without going through the maps.
import { Controller } from '@nestjs/common' import { MessagePattern } from '@nestjs/microservices' import { DefaultMicroservice } from './default.service' import { AppPattern, MicroserviceRequest, MicroserviceResponse, MessageQueues } from '@my-scope/my-common-package' @Controller() export class DefaultMicrocontroller { constructor (private readonly defaultMicroservice: DefaultMicroservice) {} @MessagePattern(AppPattern.APP_DEFAULT) public default ( options: MicroserviceRequest<MessageQueues.APP_QUEUE, AppPattern.APP_DEFAULT> ): Promise<MicroserviceResponse<MessageQueues.APP_QUEUE, AppPattern.APP_DEFAULT>> { return this.defaultMicroservice.default(options) } }
import { Controller } from '@nestjs/common' import { MessagePattern } from '@nestjs/microservices' import { DefaultMicroservice } from './default.service' import { AppMessage, AppPattern } from '@my-scope/my-common-package' @Controller() export class DefaultMicrocontroller { constructor (private readonly defaultMicroservice: DefaultMicroservice) {} @MessagePattern(AppPattern.APP_DEFAULT) public default ( options: AppMessage[AppPattern.APP_DEFAULT]['request'] ): Promise<AppMessage[AppPattern.APP_DEFAULT]['response']> { return this.defaultMicroservice.default(options) } }
- Import the module itself and since the current default is RMQ, pass in the which queues you want to connect for this instance.
import { MicroserviceProviderModule } from '@webundsoehne/nestjs-util/dist/microservices'
@Module({
imports: [
MicroserviceProviderModule.forRoot({ queue: [ ...THE_QUEUES_YOU_WANT_TO_IMPORT ] }),
]
})
export class ServerModule {}
This will automatically create a client service,
MicroserviceProviderService
, with the specified clients embeded inside.Then you can use the client in any service by injecting it. Everything will be autotyped if you use the helper type as well.
- You can inject client service through its class.
import { MicroserviceClient, MicroserviceResponse, MicroserviceProviderService } from '@my-scope/my-common-package' import { Injectable, Inject } from '@nestjs/common' @Injectable() export class DefaultService { constructor (@Inject(MicroserviceProviderService) private readonly msp: MicroserviceClient) {} public default (): Promise<MicroserviceResponse<queue, pattern>> { return this.msp.send(queue, pattern, payload) } }
- You can inject client service with a token of your choice that you can define while initializing the module.
// the service import { MicroserviceClient, MicroserviceResponse } from '@my-scope/my-common-package' import { Injectable, Inject } from '@nestjs/common' @Injectable() export class DefaultService { constructor (@Inject('MY_CLIENT_TOKEN') private readonly msp: MicroserviceClient) {} public default (): Promise<MicroserviceResponse<queue, pattern>> { return this.msp.send(queue, pattern, payload) } }
// the global module import { MicroserviceProviderModule } from '@webundsoehne/nestjs-util/dist/microservices' @Module({ name: 'MY_CLIENT_TOKEN' imports: [ MicroserviceProviderModule.forRoot({ queue: [ ...THE_QUEUES_YOU_WANT_TO_IMPORT ] }), ] }) export class ServerModule {}
This module is exported from @webundsoehne/nestjs-util/dist/microservices
and through index to not break compatability with the projects that does not have @nestjs/microservices
installed.
Decorators
Decorates provide a way to inject or override data on the function level.
Validation Override
This decorator provides a way to override the supplied Validation Pipe on a function basis.
Usage
Controller has to have the decorator on the designated path to designate the class-validator
group.
/* ... */
import { OverrideValidationOptions } from '@webundsoehne/nestjs-util'
@Controller()
export class SomeController {
@Post('some/path')
@OverrideValidationOptions({ groups: [ 'some:group' ] })
public someFunction (): Promise<void> {
return
}
}
The counter-part of group designation has to be in the given class that utilizes class-validator
.
/* ... */
export class SomeExtendedEntity extends SomeEntity {
@IsNotEmpty()
@IsOptional({ groups: [ 'some:group' ] })
dependsOnGroup?: string
}
Pipes
Extended pipes provide capabilities over the default ones for interacting with this library better.
Validation Pipe
This validation pipe extends the default pipe for class-validator
while it also provides a way to override the settings for a given path utilizing the accompanying decorator.
Usage
This can either used with a useClass
or to extend the options useFactory
. The default ValidationPipeOptions
are { whitelist: true }
.
import { Module } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'
import { ExtendedValidationPipe } from '@webundsoehne/nestjs-util'
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ExtendedValidationPipe
},
]
})
export class ServerModule {}
Exception-Filters
We implemented a generic ExceptionFilter
called GlobalExceptionFilter
, which catches all errors and set the payload to am user friendly information.
The real exception will be just logged in debug
logger mode.
Usage
import { GlobalExceptionFilter } from '@webundsoehne/nestjs-util'
@Module({
providers: [{
provide: APP_FILTER,
useClass: GlobalExceptionFilter
}]
})
class ServerModule implements NestModule {}
Http
The HttpExceptionFilter
extends from the GlobalExceptionFilter
and just catches all HttpException
errors.
It just overwrites the protected payload()
method, which builds the message for the user.
Usage
import { HttpExceptionFilter, GlobalExceptionFilter } from '@webundsoehne/nestjs-util'
@Module({
providers: [{
provide: APP_FILTER,
useClass: GlobalExceptionFilter
}, {
provide: APP_FILTER,
useClass: HttpExceptionFilter
}]
})
class ServerModule implements NestModule {}
Bad-Request
The BadRequestExceptionFilter
extends from the GlobalExceptionFilter
and just catches BadRequestException
errors.
This will handle the complex validation error messages (ValidationError
) in the overwritten payload()
method, which just happens on BadRequestException
errors.
We don't handle them in the normal HttpExceptionFilter
, because of performance reasons.
Usage
I hope you recognized that the order of the exception filters, is required.
import { BadRequestExceptionFilter, HttpExceptionFilter, GlobalExceptionFilter } from '@webundsoehne/nestjs-util'
@Module({
providers: [{
provide: APP_FILTER,
useClass: GlobalExceptionFilter
}, {
provide: APP_FILTER,
useClass: HttpExceptionFilter
}, {
provide: APP_FILTER,
useClass: BadRequestExceptionFilter
}]
})
class ServerModule implements NestModule {}
GraphQL Error Parser
Since nest.js
lets GraphQL
handle its errors its own way, graphqlErrorParser
is just a function instead of a filter. This will format the errors in the same way of the HTTP errors so you can also throw HTTP_STATUS
errors instead of plain GraphQLError
for more distinction and using the status code directly in the frontend. It will also add the GraphQL error field of which field this error is comming from.
Usage
Just add the error parser to the GraphQL Module itself.
import { GraphQLModule } from '@nestjs/graphql'
import { GraphQLErrorParser } from '@webundsoehne/nestjs-util'
import { Module } from '@nestjs/common'
@Module({
imports: [
GraphQLModule.forRoot({
formatError: GraphQLErrorParser,
})
]
})
export class ServerModule { }
RPC Global Exception
This filter will handle errors from microservices. If you use GlobalExceptionFilter
on front of it will format the errors in the same way as the RESTFUL API and you can also throw HTTP_STATUS
exceptions. This filter will also output which microservice this error is coming from for convienece of debugging.
Usage
import { Module } from '@nestjs/common'
import { APP_FILTER } from '@nestjs/core'
import { GlobalExceptionFilter } from '@webundsoehne/nestjs-util'
import { RpcGlobalExceptionFilter } from '@webundsoehne/nestjs-util/dist/microservices'
@Module({
providers: [
{
provide: APP_FILTER,
useClass: GlobalExceptionFilter
},
{
provide: APP_FILTER,
useClass: RpcGlobalExceptionFilter
}
],
imports: [ ...Object.values(modules) ]
})
class MicroservicesModule implements NestModule {
// eslint-disable-next-line @typescript-eslint/no-empty-function
async configure (): Promise<any> {}
}
return MicroservicesModule
}
This module is exported from @webundsoehne/nestjs-util/dist/microservices
and through index to not break compatability with the projects that does not have @nestjs/microservices
installed.
Cache-Lifetime
The interceptor sets the cache-lifetime information of the response as it was configured. The configuration depends normally by project and environment.
It will add a function to the request state request.state.setCacheLifetime()
, with that you can set a customize lifetime for each request.
Usage
import { RequestProfilerInterceptor } from '@webundsoehne/nestjs-util'
@Module({
providers: [{
provide: APP_INTERCEPTOR,
useClass: RequestProfilerInterceptor
}]
})
class ServerModule implements NestModule {}
setCacheLifetime()
This function takes 2 parameters, where the second one is optional.
Name | Type | Optional | Description |
---|---|---|---|
lifetime | Number | false | The lifetime of the cache in seconds |
useExpiresHeader | Boolean | true | If true the expiresHeader header will be set, otherwise the cacheControlHeader , be default it uses the configured value |
Configuration
The default values only exists in out skeleton project.
Key | Type | Default | Description |
---|---|---|---|
cacheLifetime.defaultExpiresHeader | Boolean | false | If true the expiresHeader header will be set, otherwise the cacheControlHeader |
cacheLifetime.defaultLifetime | Number | 0 | This value may set a default cache lifetime, if there was not set any before |
cacheLifetime.expiresHeader | String | 'Expires' | The header key for the expiresHeader |
cacheLifetime.cacheControlHeader | String | 'Cache-control' | The header key for the cacheControlHeader |
Request-Profiler
On debug
logger level the request profile informs you when an request got started and when it was finished.
It also logs down the information, how many seconds the request took.
Usage
import { RequestProfilerInterceptor } from '@webundsoehne/nestjs-util'
@Module({
providers: [{
provide: APP_INTERCEPTOR,
useClass: RequestProfilerInterceptor
}]
})
class ServerModule implements NestModule {}
Example
[2020-01-01T12:00:00.000Z] [debug] [RequestProfilerInterceptor] - GET /v1/hello/world starting
[2020-01-01T12:00:00.025Z] [debug] [RequestProfilerInterceptor] - GET /v1/hello/world finished - took: 0.0250 sec
Stay in touch
- Author: Backend Team
- Website: Web & Söhne
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
7 months ago
10 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago