npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

generator-kube-microservice-node

v2.5.3

Published

A k8s micro-service generator with deployment, service, Dockerfile. Built with express/mongo/redis

Downloads

82

Readme

generator-kube-microservice-node

All Contributors

Conventional Commits

A yeoman generator for nodejs micro services with TypeScript, Express, Mongoose, Redis and RabbitMQ.

Why?

This project is a boilerplate for extensible micro services written in TypeScript. Contains the minimum to instant deploy a service on a Kubernetes Cluster.

Contents

This template contains:

  • TypeScript
  • Dockerfile
  • Kubernetes deployment configuration (including Service k8s object)
  • TypeScript definition for mongo operators on controller functions
  • MongoService abstract class for all entities services
  • GenericException base for all exceptions
  • withException decorator to abstract error handling logic (used on generated controllers)
  • RemoteController class to handle axios requets
  • RabbitMQ consumers and producers logic
  • expressjs implementation with inversifyjs and inversify-express-utils

Install

  • npm i -g yo
  • npm i -g generator-kube-microservice-node
  • yo kube-microservice-node
  • Follow the inscructions

How to run

  • Run yarn dev to spawn a nodemon server watching source files
  • Create a .env file in root to handle all your secrets. Look at src/config/env.ts to see the default list of variables

Usage

Controllers

Controllers of this boilerplate are handled by inversify-express-utils package.

Here is a exemple:

@controller('/user')
export default class UserController {
  @inject(REFERENCES.UserService) private userService: UserService;

  @httpGet('/')
  @withException
  async getTenants(@response() res: Response) {
    const result = await this.tenantService.find({ throwErrors: true });
    res.status(OK).send(result);
  }

  @httpGet('/:id')
  @withException
  async getUser(@response() res: Response, @requestParam('id') id: string) {
    // Using Redis
    const [exception, result] = this.redis.withRedis({ key: 'getUser', expires: 10 }, () =>
      this.userService.findById({ id }),
    );
    if (!exception) {
      return res.status(exception.statusCode).send(exception.formatError())
    }
    return res.status(OK).send(result);
  }

There's two types of response when using MongoService:

  • A result using Either<L, R>
  • The raw entity

The two examples are described above.

Everything is injected by inversify and the composition root lives in src/config/inversify.config.ts. Your entities controllers should be imported on src/config/inversify.config.ts, so inversify-express-utils can inject your controller on express routes.

Inside the composition root, we import all controllers and inversifyjs takes care to setup our application (as seen on src/index.ts)

Services

The service layer extends the MongoService<T> which has all methods to handle the mongoose model.

import { injectable } from 'inversify';
import { MongoService } from '../shared/class/MongoService';
import { UserInterface } from '../models/UserInterface';
import { UserSchema, UserModel } from '../models/UserModel';

@injectable()
export default class UserService extends MongoService<UserInterface> {
  constructor() {
    /**
     * MongoService uses the Schema because if you change the default database while using some method from MongoService,
     * mongoose don't knows how to create the model schema for this non default database, so we help mongoose to do that
     */
    super(UserModel, UserSchema);
  }
}

Redis

Redis connection occurs when you require redis into another class. Use like this:

@controller('/user')
export default class UserController {
  @inject(REFERENCES.UserService) private userService: UserService;
  @inject(REFERENCES.RedisController) private redis: RedisController;

  @httpGet('/')
  @withException
  async getUsers(@response() res: Response) {
    const result = await this.userService.find({});
    res.status(OK).send(result);
  }

  @httpGet('/:id')
  @withException
  async getUser(@response() res: Response, @requestParam('id') id: string) {
    // This method gets a entry from cache and set it if don't exist
    const result = this.redis.withRedis({ key: 'getUser', expires: 10 }, () =>
      this.userService.findById({ id, throwErrors: true }),
    );
    if (!result) {
      throw new EntityNotFoundException({ id });
    }
    res.status(OK).send(result);
  }

RabbitMQ

To use a consume/producer function for RabbitMQ, bootstrap the connection on your Service like this:


@injectable()
export default class UserService extends MongoService<UserInterface> {

  @inject(REFERENCES.EventBus) private eventBus: EventEmitter;
  private _channel: Channel;
  constructor() {
    super(UserModel, UserSchema);
    // Only connect to Rabbit when mongo is connected
    this.eventBus.on('mongoConnection', this._createRabbitMQChannelAndSetupQueue);
    // Reconnect to rabbitmq
    this.eventBus.on('reconnectRabbitMQ', this._createRabbitMQChannelAndSetupQueue);
  }

  /**
 * Creates a RabbitMQ Channel and setup the queue for this service
 */
  // Run this function on constructor
  private async _createRabbitMQChannelAndSetupQueue() {
    this._channel = await createRabbitMQChannel(env.rabbitmq_url);
    // Some consumer on ./src/queue/consumers
    consumeCreateUser(this._channel, this._consumeCreateUser);
  }
  /**
   * RabbitMQ Consumer CREATE_USER Function
   *
   * Creates a user
   * @param payload RabbitMQ ConsumeMessage type.
   */
  private _consumeCreateUser = async (payload: ConsumeMessage) => {
    const data = JSON.parse(payload.content.toString());
    /** DO SOMETHING  */
    this._channel.ack(payload); // sends a acknowledgement
  };
}

The producer is straight forward: just call the function that sends something to a queue (ex: ./src/queue/producers/)

Exceptions

All exceptions that are catch by src/server/middlewares/index.ts, have GenericException as they base.

So, just continuing throw new errors based on GenericException.ts that express will catch and handle. (see src/shared/exceptions/ folder for default exceptions created)

Service authorization

In src/server/ you can find a Unauthorized.ts file that handles authorization logic of this service.

Using this middleware, you should have another service with endpoint /auth that receives a JWToken via Authorization header.

If that service responds with 200, you're authorized to procced with your request into this service.

To use it, just insert into src/server/ServerFactory.ts a line containing this middleware

import * as bodyParser from 'body-parser';
import * as compression from 'compression';
import * as cors from 'cors';
import * as express from 'express';
import { RouteNotFoundMiddleware, ExceptionMiddleware } from './middlewares';
import Unauthorized from './Unauthorized';

export default {
  initExternalMiddlewares(server: express.Application) {
    server.use(compression());
    server.use(bodyParser.json());
    server.use(cors());
  },
  initExceptionMiddlewares(server: express.Application) {
    // New Line!!!
    server.use(Unauthorized)
    server.use(RouteNotFoundMiddleware);
    server.use(ExceptionMiddleware);
  },
};

Dependency Injection

This template uses inversifyjs to handle DI with a IoC container. The file that handles that is src/config/inversify.config.ts

import '../entities/User/UserController';
import '../shared/middlewares/HealthCheck';

import { Container } from 'inversify';

import REFERENCES from './inversify.references';
import Connection from '../shared/class/Connection';
import UserService from '../entities/User/UserService';
import RemoteController from '../shared/class/RemoteController';

const injectionContainer = new Container({ defaultScope: 'Singleton' });

injectionContainer.bind(REFERENCES.Connection).to(Connection);
injectionContainer.bind(REFERENCES.RemoteController).to(RemoteController);
injectionContainer.bind(REFERENCES.UserService).to(UserService);


export default injectionContainer;

If your controller has another class dependency, inject the dependency onto your class like this:

export default class UserController {
  @inject(REFERENCES.UserService) private userService: UserService;
}

Docker and Kubernetes

To build a docker image, you have to build the project using npm run build and npm run build:webpack. Then, use npm run build:docker, and to publish, use npm run publish:docker. Remember to edit these commands if you use private repos.

The Kubernetes deployment file (deployment.yaml), has a LivenessProbe that checks if the route /health returns 200. This route, pings to the database. If something goes wrong, your service will be restarted.

The Service object in deployment.yaml file expose the Pod created by the Deployment to the world on port 80 and binding the port 3000 of the Pod to it.

After configuring, you need to add the Service definition in a ingress controller of your k8s cluster.

Since this template uses Kubernetes, the .dockerignore and Dockerfile files DOESN'T have a reference to .envfile (which, also is ignored on .gitignore file). The way to go about it is setting a envFrom field on deployment.yaml.

Here is a example:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 4
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: <some-image>
        ports:
          - containerPort: 3000
        envFrom:
        - configMapRef:
            name: env-config
        livenessProbe:
          initialDelaySeconds: 20
          periodSeconds: 5
          httpGet:
            path: /health
            port: 3000

Contributing

PR's and new issues are welcome. Anything you think that'll be great to this project will be discussed.

Development

Clone this repo, then, npm install and npm link. Now you can test this generator locally using yo command.

Acknowledgements

Many thanks for the folks that worked hard on:

  • inversifyjs (https://github.com/inversify/InversifyJS)
  • inversify-express-utils (https://github.com/inversify/inversify-express-utils)

Without these libs, this boilerplate doesn't exists

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!