Things you must have in every Repo for Javascript

tkssharma - Mar 30 '22 - - Dev Community

Best way to baseline nestjs Microservice

Originally Published here https://tkssharma.com/nestjs-microservice-baseline-setup

There is no specific way or standard way of doing it, but we can maintain minimum standards of writing code or service like

  • Moduel structure
  • shared and feature Modules
  • proper test setup for e2e and unit tests
  • proper test environment
  • proper setup for docker for local development providing (dev and test DB both)
  • proper eslint setup with prettierc
  • commit lint setup for proper commit messages
  • docker and docker compose for spinning local environments
  • jest configurations for E2E and unit tests
  • compiler configuration for test and src application code
  • husky hook to enforce commitlint
  • CI pipeline configurations

Lets start a basic app using nestjs CLI

i hope you already know hoe to create basic nestjs app using CLI
Nest.js Application
Let’s continue with NestJS! We are going to install the NestJS CLI, so open the terminal of your choice and type:



$ npm i -g @nestjs/cli
$ nest new nest-env



Enter fullscreen mode Exit fullscreen mode

We initialize a new NestJS project with its CLI. That might take up to a minute. The “-p npm” flag means, that we going to choose NPM as our package manager. If you want to choose another package manager, just get rid of this flag.
After this command is done you can open your project in your code editor. Since I use Visual Studio Code, I gonna open the project by typing:



$ cd nest-env
$ code .


Enter fullscreen mode Exit fullscreen mode

Now we want to have our file structure Look like this, its our final Goal, lets see how we can achieve this

'baseline code using nestjs'

Now lets follow what all things we need to have this setup for our application

Step-1 commitlint and Husky Git Hooks

we need husky Hooks to enforce commit lint for our code with commitlint
Lets first install https://github.com/conventional-changelog/commitlint module

What is commitlint

commitlint checks if your commit messages meet the conventional commit format.

In general the pattern mostly looks like this:

  • type(scope?): subject #scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",")
    Real world examples can look like this:

  • chore: run tests on travis ci

  • fix(server): send cors headers

  • feat(blog): add comment section



  "devDependencies": {
    "@commitlint/cli": "15.0.0",
    "@commitlint/config-conventional": "15.0.0",
    "commitizen": "^4.2.4",
    "cz-conventional-changelog": "^3.3.0",

  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
  }


Enter fullscreen mode Exit fullscreen mode

commitlint.config.js



module.exports = { extends: ["@commitlint/config-conventional"] };


Enter fullscreen mode Exit fullscreen mode

With the help of Git Hooks, you can run scripts automatically every time a particular event occurs in a Git repository. With this amazing feature of Git and with the help of Husky, You can lint your commit messages, prettify the whole project’s code, run tests, lint code, and … when you commit.
So here we can have a meeting between commitlint and husky git hooks, we want to run commit lint to check commit messages using hooks which Husly is managing
so Husky and commitlint will work for us

example like a simple git Hook, it is using commitlint Module to tun this script



#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

if [ "$NO_VERIFY" ]; then exit 0; fi
exec < /dev/tty && node_modules/.bin/cz --hook || true


Enter fullscreen mode Exit fullscreen mode


npm install husky --save-dev


Enter fullscreen mode Exit fullscreen mode

Enable Git hooks



npx husky install


Enter fullscreen mode Exit fullscreen mode

To automatically have Git hooks enabled after install, edit package.json
npm set-script prepare "husky install"
You should have:



{
  "scripts": {
    "prepare": "husky install"
  }
}


Enter fullscreen mode Exit fullscreen mode


# commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1


Enter fullscreen mode Exit fullscreen mode


# pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint && npm run prettier


Enter fullscreen mode Exit fullscreen mode


#  prepare-commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

if [ "$NO_VERIFY" ]; then exit 0; fi
exec < /dev/tty && node_modules/.bin/cz --hook || true


Enter fullscreen mode Exit fullscreen mode

From above configuration, make sure we have all these 3 script in .husky folder in the root
Husky Git hooks are using commit lint to provide proper commit message syntex and also running lint with prettier command to clean the linting
of the code, These all three Hooks works to place best standards for our code.

Step-2 Docker setup for local and test env

we need docker setup with docker-compsoe files, our container depends on what we are doing in service like

  • Node JS container
  • Postgres container
  • redis container
  • rabbit MQ container etc etc

docker-compose.yml



version: "3.6"
services:
  node:
    build: .
    volumes:
      - .:/app
      - ~/.npmrc/:/root/.npmrc
  postgres:
    image: postgres
    restart: unless-stopped  



Enter fullscreen mode Exit fullscreen mode

docker file



FROM node:12-buster-slim

WORKDIR /app

COPY package.json package-lock.json /app/

RUN npm install && \
    rm -rf /tmp/* /var/tmp/*

COPY ./docker-utils/entrypoint/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

COPY . /app

RUN npm run build

EXPOSE 3000

USER node

ENV TYPEORM_MIGRATION=ENABLE
ENV NPM_INSTALL=DISABLE
CMD npm run start:prod


Enter fullscreen mode Exit fullscreen mode

docker-compose.override.yml



version: "3"
services:
   node:
     container_name: document_node
     command: npm run start
     environment:
       NPM_INSTALL: ENABLE
       TYPEORM_MIGRATION: ENABLE
     ports:
       - 3000:3000
  postgres:
    environment:
      - POSTGRES_USER=api
      - POSTGRES_PASSWORD=development_pass
      - POSTGRES_MULTIPLE_DATABASES="example-api","example-api-testing"
    volumes:
      - ./docker-utils:/docker-entrypoint-initdb.d
      - api_data:/data/postgres
    ports:
      - 5434:5432
volumes:
  api_data: {}


Enter fullscreen mode Exit fullscreen mode

With all this we also wants to bootstrap postgres container with init database so we don't have to create manually



    environment:
      - POSTGRES_USER=api
      - POSTGRES_PASSWORD=development_pass
      - POSTGRES_MULTIPLE_DATABASES="example-api","example-api-testing"
    volumes:
      - ./docker-utils:/docker-entrypoint-initdb.d


Enter fullscreen mode Exit fullscreen mode

This we can do with script and same script we can mount to volume as init script
we can craete script inside docker-utils folder



#!/bin/bash
set -e
set -u

function create_user_and_database() {
    local database=$1
    echo "  Creating user and database '$database'"
    psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
        CREATE USER $database;
        CREATE DATABASE $database;
        GRANT ALL PRIVILEGES ON DATABASE $database TO $database;
EOSQL
}

if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
    echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
    for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do
        create_user_and_database $db
    done
    echo "Multiple databases created"
fi


Enter fullscreen mode Exit fullscreen mode

Step-3 setting up eslint and prettier

Prettier can be run as a plugin for ESLint, which allows you to lint and format your code with a single command. Anything you can do to simplify your dev process is a win in my book. Prettier + ESLint is a match made in developer heaven.
If you’ve ever tried to run Prettier and ESLint together, you may have struggled with conflicting rules. Don’t worry! You’re not on your own. Plug in eslint-config-prettier, and all ESLint’s conflicting style rules will be disabled for you automatically.



    "@typescript-eslint/eslint-plugin": "4.33.0",
    "@typescript-eslint/parser": "4.33.0",
    "eslint": "7.32.0",
    "eslint-config-prettier": "8.3.0",
    "eslint-plugin-prettier": "4.0.0",
    "eslint-plugin-unused-imports": "2.0.0",


Enter fullscreen mode Exit fullscreen mode

.eslintrc



module.exports = {
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint"
  ],
  "rules": {
    // strict https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md
    // strict which we may not be able to adopt
    "@typescript-eslint/explicit-module-boundary-types": 0,
    // allow common js imports as some deps are still with common-js 
    "@typescript-eslint/no-var-requires": 0,
    "no-useless-escape": 0,
    // this is for regex, it will throw for regex characters
    "no-useless-catch": 0,
    // this i have to add as we mostly use rollbar to catch error 
    "@typescript-eslint/no-explicit-any": 0,
    // this is needed as we assign things from process.env which may be null | undefined | string 
    // and we have explicitly this.configService.get().azure.fileUpload.containerName!
    "@typescript-eslint/no-non-null-assertion": 0,
    "no-async-promise-executor": 0
  }
};


Enter fullscreen mode Exit fullscreen mode

prettierc



{
  "bracketSpacing": true,
  "printWidth": 80,
  "proseWrap": "preserve",
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "tabWidth": 4,
  "useTabs": true,
  "parser": "typescript",
  "arrowParens": "always",
  "requirePragma": true,
  "insertPragma": true,
  "endOfLine": "lf",
  "overrides": [
    {
      "files": "*.json",
      "options": {
        "singleQuote": false
      }
    },
    {
      "files": ".*rc",
      "options": {
        "singleQuote": false,
        "parser": "json"
      }
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode

And we can add required scripts to have linters enabled



    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --quiet",
    "prettier": "./node_modules/.bin/prettier --check \"**/*.{js,json,ts,yml,yaml}\"",
    "prettier:write": "./node_modules/.bin/prettier --write \"**/*.{js,json,ts,yml,yaml}\"",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",


Enter fullscreen mode Exit fullscreen mode

Step-4 Build Configurations using tsconfigs

The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project.

JavaScript projects can use a jsconfig.json file instead, which acts almost the same but has some JavaScript-related compiler flags enabled by default.
Its good to have one global tsconfig and rest we can also create tsocnfig for build and test like tsconfig.build.json and using this config for build



{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2017",
    "allowJs": true,
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "strict": true,
    "skipLibCheck": true,
    "paths": {
      "@app/*": ["src/app/*"],
      "@auth/*": ["src/app/auth/*"]
    }
  },
}


Enter fullscreen mode Exit fullscreen mode

tsocnfig Build configuration, we can override anything we want, we are extending base config and changing things if needed



{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "rootDir": "./",
    "declaration": false,
    "removeComments": true,
    "sourceMap": false,
    "incremental": false
  },
  "exclude": ["node_modules", "coverage", "test", "build","dist", "**/*spec.ts", "**/*mocks.ts"]
}


Enter fullscreen mode Exit fullscreen mode

step-5 test related configurations

In most of the Projects we are using jest now
In test Folder we can have setEnvVars which will populate test config in process.env



const dotenv = require('dotenv');
dotenv.config({ path: './env.test' });


Enter fullscreen mode Exit fullscreen mode


module.exports = {
  setupFiles: ['<rootDir>/test/setEnvVars.js'],
  silent: false,
  moduleFileExtensions: ['js', 'ts'],
  rootDir: '.',
  testRegex: '[.](spec|test).ts$',
  transform: {
    '^.+\\.(t|j)s$': 'ts-jest',
  },
  coverageDirectory: './coverage',
  testEnvironment: 'node',
  roots: ['<rootDir>/'],
  moduleNameMapper: {
    "^@app(.*)$": "<rootDir>/src/app/$1",
    "^@auth(.*)$": "<rootDir>/src/app/auth/$1"
  },
};


Enter fullscreen mode Exit fullscreen mode

Rest all the configurations are project related like ormconfig.ts or knex.ts

  • ormconfig.ts and knex.ts ORM related config
  • nodemon.json or nest-cli.json nestjs cli configuration
  • env and env.test files for local and test env
  • CI configuration files
  • Any other APM related file like newrelic.js
  • deployment related file like procfile for Heroku

Conclusion

As we are building and working in everyday changing environemnt, its better to have a base template which we can keep evolving day by day and easy to start for any new project
from ground Zero, I hope above example can help you to build your template for different services

References

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .