0.0.2 • Published 1 year ago

@steplix/nestjs-microservice v0.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

@steplix/nestjs-microservice

Based NodeJs + NestJs microservice.

Index

Download & Install

Install

npm i @steplix/nestjs-microservice

Update

npm up @steplix/nestjs-microservice

Source code

git clone https://github.com/steplix/pkg-nestjs-microservice
cd ms-nestjs-microservice
npm i

Publish

# on NPM
npm run build
npm publish

# or
# on GIT
git add .
git commit -m "Support for ..."
git push origin develop

How is it used?

NOTE: Based on NestJs.

Recommended structure

|
| -> src
|    | -> config                    <- (optional) MS configurations
|    |
|    | -> constants                 <- (optional) MS constants
|    |
|    | -> entities                  <- (optional) Mapped DB entities
|    |    | -> your-entity-class.ts <- Ej. "pet"
|    |    | -> pet.ts <--------------------\___/
|    |
|    | -> modules                   <- Nest js MS module
|    |    | -> your-module-name     <- Ej. "pets"
|    |    | -> pets <----------------------\____/
|    |    |    | -> controller.ts   <- Entry point controller
|    |    |    |
|    |    |    | -> service.ts      <- Service in charge of resolving what is requested by the client
|    |    |    |
|    |    |    | -> module.ts       <- Module in charge of mapping everything necessary for the correct operation within the nest js environment
|    |
|    | -> app.module.ts             <- Base module
|    |
|    | -> app.ts                    <- NestJs application logic
|    |
|    | -> main.ts                   <- Application trigger (start point)
|

Configure

For app/server

Environment variableValuesTypeDefault valueDescription
SERVER_PORT / PORTport numbernumber8000Port where the server listens
SERVER_HEALTH_PATHhealth check pathstring"/health"Route for health check endpoint
HEALTH_FAILURE_ON_ERRORhealth check failure on errorbooleanfalseIndicate if health check endpoint return status != 200 on error
HEALTH_LOG_HTTP_ACCESShealth check log http accessbooleanfalseIndicate if log http access for health check endpoint

For database

Simple configuration
Environment variableValuesTypeDefault valueDescription
DB_ENABLEDtrue/falsebooleantrueIndicate if need support for database
DB_ENTITIES_DIRentities pathnamestring'entities/!(index).js'Indicate location entities class
DB_NAMEdb namestringnull
DB_HOSTdb host namestring'localhost'
DB_PORTdb portnumber3306
DB_USERdb userstring'username'
DB_PASSdb passwordstringnull
DB_TIMEZONEdb timezonestring'+00:00'
Read/Write replication configuration
Environment variableValuesTypeDefault valueDescription
DB_NAME_WRITEdb namestringDB_NAMEDB name for write replication
DB_HOST_WRITEdb host namestringDB_HOSTDB host name for write replication
DB_PORT_WRITEdb portnumberDB_PORTDB port for write replication
DB_USER_WRITEdb userstringDB_USERDB username for write replication
DB_PASS_WRITEdb passwordstringDB_PORTDB password for write replication
DB_NAME_READdb namestringDB_NAMEDB name for read replication
DB_HOST_READdb host namestringDB_HOSTDB host name for read replication
DB_PORT_READdb portnumberDB_PORTDB port for read replication
DB_USER_READdb userstringDB_USERDB username for read replication
DB_PASS_READdb passwordstringDB_PORTDB password for read replication

For debug/logger

Environment variableValuesTypeDefault valueDescription
LOGGER_NAME / APP_NAMEapplication namestring'app'Description used for logger
LOGGER_ROUTESpattern for log requeststring"*"Pattern for filter request paths
LOGGER_LEVELlogger levelstring"debug"Logger level

For communication between microservices

Environment variableValuesTypeDefault valueDescription
SERVICE_BASE_URLprotocol + domainstring''Base MS URL. The port will be concatenated automatically. Example, http://localhost
SERVICE_{MS NAME}_URLprotocol + full domainstring''Full MS URL. Example, http://localhost:{MS PORT}
SERVICE_{MS NAME}_HEALTHHealth pathnamestring"/health"Route for health check endpoint

For services

Environment variableValuesTypeDefault valueDescription
SERVICE_AGENTservice agent namestring''
SERVICE_CONNECTION_TIMEOUTnumber in milisecondsnumber21000
SERVICE_REQUEST_TIMEOUTnumber in milisecondsnumber20000
SERVICE_DEPENDENCIESmicroservices relatedstring''Indicate list of microservices dependencies

For statics/publics assets

Environment variableValuesTypeDefault valueDescription
STATICS_ENABLEDenabled staticsbooleanfalseIndicate if is available statics (/public)
STATICS_SERVE_ROOTstatics pathnamestring'/public'
STATICS_ROOT_PATHstatics dir locationstring'../public'

For JWT

Environment variableValuesTypeDefault valueDescription
JWT_ALGORITHMcrypto algorithmstringnull
JWT_PUBLIC_KEYpublic keystringnull
JWT_PRIVATE_KEYprivate keystringnull
JWT_PUBLIC_KEY_FILEpath file public keystringnull
JWT_PRIVATE_KEY_FILEpath file private keystringnull
JWT_SECRETpublic and private keystring"SECRET"Only used when public and/or private key is not defined

For Cache

Environment variableValuesTypeDefault valueDescription
CACHE_ENABLEDtrue/falsestringtrueIndicate if is eanble this feature
CACHE_TYPEmemory/redisstring'memory'

Redis

Environment variableValuesTypeDefault valueDescription
CACHE_HOSTredis hostnamestring'localhost'
CACHE_PORTredis port numbernumber6379

For Tracking

Environment variableValuesTypeDefault valueDescription
TRACKING_ENABLEDtrue/falsebooleantrueIndicate if send requests to tracking service
TRACKING_DEBUGtrue/falsebooleanfalseIndicate if debug all success or failure request to tracking service
TRACKING_PREFIXapplication namestringpkg.name / 'app'Description used for base prefix tracking event name

For documentation

Environment variableValuesTypeDefault valueDescription
DOC_SWAGGER_URLDoc pathnamestring'/api/doc'
DOC_SWAGGER_TITLEDoc titlestringpackage.json name
DOC_SWAGGER_DESCRIPTIONDoc descriptionstringpackage.json description
DOC_SWAGGER_VERSIONDoc versionstringpackage.json version
DOC_SWAGGER_AUTH_TOKENDoc auth token namestring'token'Cookie auth name
DOC_SWAGGER_SERVERBase API serverstring'localhost:<port>'Indicate base URL for try it out endpoints

Communication between microservices

const { Service } = require('@steplix/nestjs-microservice');

// ...

const usersService = Service.get('users');

// Use HTTP method GET - for get user by ID.
const user = await usersService.get(`/api/v1/users/${userId}`);

// Use HTTP method GET - for get only one user. (Take the first orden on list)
const user = await usersService.getOne('/api/v1/users');

// Use HTTP method POST - for create an user.
const user = await usersService.post({
  uri: '/api/v1/users',
  data: payload
});

// Use HTTP method PUT - for update an user. (WARNING: This endpoint does not exists. Is only explanatory)
const user = await usersService.put({
  uri: `/api/v1/users/${userId}`,
  data: payload
});

// Use HTTP method PATCH - for partial update an user. (WARNING: This endpoint does not exists. Is only explanatory)
const user = await usersService.patch({
  uri: `/api/v1/users/${orderId}/status`,
  data: {
    statusId: 0
  }
});

// ...

By default, the microservice base URL is "http://localhost:{MS PORT}". Example for Subscriptions "http://localhost:3001"

For configure other MS URL, use environment configuration

Available services
  • auth
  • users
  • locations
  • notifications
  • feature_toggler

💡 NOTE: To configure more services, add the same ones in config/services.ts constant remoteServices;

Interceptors

Authenticate user by JWT
const { RequiredAuthUserInterceptor } = require('@steplix/nestjs-microservice');

...
export class SubscriptionsController {
  ...
  @Post()
  @UseInterceptors(RequiredAuthUserInterceptor)
  create(@Req() req: Request, @Body() body: CreateSubscriptionDto) {
    body.user = (req as any).user;
    ...
  }
  ...
}
Cache controller endpoints
const { CacheInterceptor, Cache } = require('@steplix/nestjs-microservice');

...
@UseInterceptors(CacheInterceptor)
export class SubscriptionsController {
  ...
  @Get()
  @Cache()
  find(@Query query: any) {
    ...
  }
  ...
  @Get()
  @Cache({
    time: "6 hours"
  })
  getById(@Query query: any) {
    ...
  }
  ...
  @Get()
  @Cache({
    key: "my-custom-key"
  })
  listSubscribableProducts() {
    ...
  }
  ...
  @Get()
  // No cache, for this endpoint
  @Cache(false)
  listWrongSubscriptions() {
    ...
  }
  ...
}
Available interceptors
  • RequiredAuthUserInterceptor
  • AuthUserInterceptor
  • CacheInterceptor

Database

For configure credentials, use .env file. For example,

...

DB_DEBUG=true
DB_ENABLED=true
DB_AUTO_DISCOVER=true
DB_PASS=WwFFTRDJ7s2RgPWx
DB_NAME=steplixpp_subscriptions
DB_HOST=localhost
DB_USER=root

...

All entities are need mapping on <micro service root dir>/src/entities. For example,

Model <micro service root dir>/src/entities/subscription.ts

import {
  Table,
  Column,
  PrimaryKey,
  ForeignKey,
  BelongsTo,
  HasMany,
} from "sequelize-typescript";
import { ApiProperty } from "@nestjs/swagger";
import { Model, Remote } from "@steplix/nestjs-microservice";
import SubscriptionProduct from "./subscriptionProduct";
import SubscriptionStatus from "./subscriptionStatus";
import SubscriptionOrder from "./subscriptionOrder";

@Table({ tableName: "subscriptions" })
export default class Subscription extends Model<Subscription> {
  @ApiProperty({ description: "Unique identifier" })
  @PrimaryKey
  @Column
  id: number;

  @ApiProperty({ description: "Unique User identifier" })
  @Column({ field: "user_id" })
  userId: number;

  @ApiProperty({ description: "Unique Subscription Status identifier" })
  @ForeignKey(() => SubscriptionStatus)
  @Column({ field: "subscription_status_id" })
  statusId: number;

  ...

  @BelongsTo(() => SubscriptionStatus)
  status: SubscriptionStatus;

  @HasMany(() => SubscriptionProduct)
  products: SubscriptionProduct[];

  @HasMany(() => SubscriptionOrder)
  orders: SubscriptionOrder[];

  @Remote({
    uri: ({ model }) => `/v1/users/${model.userId}`,
  })
  user: any;
}

Decorators

Cache

This decorator works in conjunction with the cache interceptor.

const { CacheInterceptor, Cache } = require('@steplix/nestjs-microservice');

...
@UseInterceptors(CacheInterceptor)
export class SubscriptionsController {
  ...
  @Get()
  @Cache()
  find(@Query query: any) {
    ...
  }
  ...
  @Get()
  @Cache({
    time: "6 hour"
  })
  getById(@Query query: any) {
    ...
  }
  ...
  @Get()
  @Cache({
    key: "my-custom-key"
  })
  listSubscribableProducts() {
    ...
  }
  ...
  @Get()
  // No cache, for this endpoint
  @Cache(false)
  listWrongSubscriptions() {
    ...
  }
  ...
}

The available settings of this decorator are,

{
  // Cache key
  key?: string | (ctx: ExecutionContext) => Promise<string | undefined> | string | undefined;

  // Cache time in milliseconds
  time?: number | string | (ctx: ExecutionContext) => Promise<number | undefined> | number | undefined;

  // Indicate if the endpoint is exclude from cache
  exclude?: boolean | (ctx: ExecutionContext) => Promise<boolean | undefined> | boolean | undefined;
}
Remote

This decorator is used to indicate that a property is remote and how its value should be popular.

typescript
...

@Table({ tableName: "subscriptions" })
export default class Subscription extends Model<Subscription> {

  ...

  @ApiProperty({ description: "Unique User identifier" })
  @Column({ field: "user_id" })
  userId: number;

  ...

  @Remote({
    uri: ({ model }) => `/v1/users/${model.userId}`,
  })
  user: any;
}

In this example, you can see how it was indicated that the user property is remote. For the same, it was indicated that url is the one that responds with said data. To popularize this value, the services are used. To know which service to use, use the name of the property.

The available settings of this decorator are,

{
  // Remote service name
  service?: string | (...args: any[]) => any;

  // URI for call remote service
  uri?: string | (...args: any[]) => any;

  // HTTP Method used for call remote service
  method?: string | (...args: any[]) => any;

  // Axios request config
  options?: AxiosRequestConfig | (...args: any[]) => any;

  // Indicates whether the dependency is mandatory or not
  required?: boolean | (...args: any[]) => any;

  // Indicates if the error should be printed if the request does not fail
  silent?: boolean | (...args: any[]) => any;

  // Remote field to resolve
  remoteField?: string;
}

Logger

const { logger } = require('@steplix/nestjs-microservice');

logger.error(new Error('Not Found'));
logger.error('This is an error');
logger.warn('This is a warning');
logger.info('Hello World!');
logger.debug('Hello World!');

Tracking

const { tracking } = require('@steplix/nestjs-microservice');

// Increment: Increments a stat by a value (default is 1)
tracking.increment('my_counter');
// Incrementing multiple items
tracking.increment(['these', 'are', 'different', 'stats']);
// Sampling, this will sample 25% of the time the StatsD Daemon will compensate for sampling
tracking.increment('my_counter', 1, 0.25);

// Decrement: Decrements a stat by a value (default is -1)
tracking.decrement('my_counter');

// Gauge: Gauge a stat by a specified amount
tracking.gauge('my_gauge', 123.45);

// Timing: sends a timing command with the specified milliseconds
tracking.timing('response_time', 42);

// Histogram: send data for histogram stat
tracking.histogram('my_histogram', 42);
// Tags, this will add user-defined tags to the data
tracking.histogram('my_histogram', 42, ['foo', 'bar']);
// Sampling, tags and callback are optional and could be used in any combination
tracking.histogram('my_histogram', 42, 0.25); // 25% Sample Rate
tracking.histogram('my_histogram', 42, ['tag']); // User-defined tag
tracking.histogram('my_histogram', 42, next); // Callback
tracking.histogram('my_histogram', 42, 0.25, ['tag']);
tracking.histogram('my_histogram', 42, 0.25, next);
tracking.histogram('my_histogram', 42, ['tag'], next);
tracking.histogram('my_histogram', 42, 0.25, ['tag'], next);

// Set: Counts unique occurrences of a stat (alias of unique)
tracking.unique('my_unique', 'foobarbaz');
tracking.set('my_unique', 'foobar');
tracking.set(['foo', 'bar'], 42, function(error, bytes) {
  // this only gets called once after all messages have been sent
  if (error) {
    console.error('Oh noes! There was an error:', error);
  } else {
    console.log('Successfully sent', bytes, 'bytes');
  }
});

Tests

In order to see more concrete examples, I INVITE YOU TO LOOK AT THE TESTS :)

Run the unit tests

npm test