DEV Community

Cover image for Using Consul-KV in NestJS
ILshat Khamitov
ILshat Khamitov

Posted on

5

Using Consul-KV in NestJS

Easily work with Consul Key/Value Store

Links

https://developer.hashicorp.com/consul/docs/dynamic-app-config/kv - Key/Value (KV) Store

https://www.npmjs.com/package/ilink-console-tools - console. utility for upload/download env files to/from consul-kv

https://www.npmjs.com/package/nestjs-consul-kv-realtime - NestJS module for realtime work with Consul-KV

https://github.com/EndyKaufman/nestjs-consul-example - project with code from this post

Steps to create a project using nest and consul

1. Create NestJS project

a) Create a new NestJS application with @nestjs/cli



npm i -g @nestjs/cli
nest new nestjs-consul-example
cd nestjs-consul-example


Enter fullscreen mode Exit fullscreen mode

b) Install dependencies



npm i --save consul nestjs-consul-kv-realtime


Enter fullscreen mode Exit fullscreen mode

c) Install devDependencies



npm i --save-dev ilink-console-tools @types/consul


Enter fullscreen mode Exit fullscreen mode

2. Add work with Docker-compose

a) Install docker-compose

https://docs.docker.com/compose/install

b) Create nestjs-consul-example/docker-compose.yml



version: "3"
networks:
  nestjs-consul-example-network:
    driver: bridge

services:
  nestjs-consul-example:
    image: bitnami/consul:latest
    container_name: "nestjs-consul-example"
    environment:
      - CONSUL_HTTP_TOKEN=${CONSUL_TOKEN_MASTER}
    networks:
      - nestjs-consul-example-network
    ports:
      - "8300:8300"
      - "8301:8301"
      - "8301:8301/udp"
      - "8500:8500"
      - "8600:8600"
      - "8600:8600/udp"


Enter fullscreen mode Exit fullscreen mode

c) Create nestjs-consul-example/env.docker-compose



CONSUL_HTTP_TOKEN=e2999fc6-1fc1-4345-a56e-e9d27b34c1c1


Enter fullscreen mode Exit fullscreen mode

d) Add new scripts to nestjs-consul-example/package.json



{
  "scripts": {
    "__________dev infra__________": "__________dev infra__________",
    "docker:dev:restart": "npm run docker:dev:down && npm run docker:dev:up",
    "docker:dev:up": "set -a && . ./env.docker-compose && set +a && export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./docker-compose.yml --compatibility up -d",
    "docker:dev:down": "set -a && . ./env.docker-compose && set +a && export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./docker-compose.yml down"
  }
}


Enter fullscreen mode Exit fullscreen mode

e) Start docker-compose



npm run docker:dev:restart


Enter fullscreen mode Exit fullscreen mode

3. Add default environment variables to consul

a) Create nestjs-consul-example/env.default



HELLO_MESSAGE="Hello from ENV file!"


Enter fullscreen mode Exit fullscreen mode

b) Add new script and update exist in nestjs-consul-example/package.json



{
  "scripts": {
    "__________dev infra__________": "__________dev infra__________",
    "docker:dev:restart": "npm run docker:dev:down && npm run docker:dev:up && npm run docker:dev:fill-default-data",
    "docker:dev:up": "set -a && . ./env.docker-compose && set +a && export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./docker-compose.yml --compatibility up -d",
    "docker:dev:down": "set -a && . ./env.docker-compose && set +a && export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./docker-compose.yml down",
    "docker:dev:fill-default-data": "set -a && . ./env.docker-compose && set +a && ilink-console-tools env-to-consul --path=./env.default --consul-token=$CONSUL_HTTP_TOKEN --consul-clear=true"
  }
}


Enter fullscreen mode Exit fullscreen mode

c) Restart docker-compose



npm run docker:dev:restart


Enter fullscreen mode Exit fullscreen mode

d) Navigate to http://localhost:8500/ui/dc1/kv/env/ and check data in UI

content nestjs-consul-example/env.default in consul

4. Add NestjsConsulKvRealtimeModule to the application

a) Update nestjs-consul-example/src/app.module.ts



import { Module } from '@nestjs/common';
import { NestjsConsulKvRealtimeModule } from 'nestjs-consul-kv-realtime';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    NestjsConsulKvRealtimeModule.forRootAsync({
      useFactory: async () => ({
        port: '8500',
        host: 'localhost',
        defaults: {
          token: process.env.CONSUL_HTTP_TOKEN,
        },
      }),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


Enter fullscreen mode Exit fullscreen mode

b) Update nestjs-consul-example/src/app.controller.ts



import { Controller, Get } from '@nestjs/common';
import { ConsulKeyValue } from 'nestjs-consul-kv-realtime';
import { AppService } from './app.service';

@Controller()
export class AppController {
  @ConsulKeyValue({
    key: 'env',
  })
  consulEnvironments!: { HELLO_MESSAGE: string };

  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('consul')
  getConsulHello(): string {
    return this.consulEnvironments.HELLO_MESSAGE;
  }
}


Enter fullscreen mode Exit fullscreen mode

c) Update nestjs-consul-example/tsconfig.json



{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,
    "esModuleInterop": true
  }
}


Enter fullscreen mode Exit fullscreen mode

d) Add new scripts to nestjs-consul-example/package.json



{
  "scripts": {
    "start:dev": "set -a && . ./env.docker-compose && set +a && nest start --watch",
    "test": "npm run docker:dev:fill-default-data && set -a && . ./env.docker-compose && set +a && jest --forceExit",
  }
}


Enter fullscreen mode Exit fullscreen mode

5. Update tests and test them

a) Update exist test nestjs-consul-example/src/app.controller.spec.ts



import { Test, TestingModule } from '@nestjs/testing';
import Consul from 'consul';

import { NestjsConsulKvRealtimeModule } from 'nestjs-consul-kv-realtime';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let appController: AppController;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      imports: [
        NestjsConsulKvRealtimeModule.forRootAsync({
          useFactory: async () => ({
            port: '8500',
            host: 'localhost',
            defaults: {
              token: process.env.CONSUL_HTTP_TOKEN,
            },
          }),
        }),
      ],
      controllers: [AppController],
      providers: [AppService],
    }).compile();
    await app.init();

    appController = app.get<AppController>(AppController);
  });

  describe('root', () => {
    it('should return "Hello World!"', () => {
      expect(appController.getHello()).toBe('Hello World!');
    });

    it('should return "Hello from ENV file!"', async () => {
      expect(appController.getConsulHello()).toBe('Hello from ENV file!');
    });

    it('should return "Hello from TEST!"', async () => {
      const consul = new Consul({
        port: '8500',
        host: 'localhost',
        defaults: {
          token: process.env.CONSUL_HTTP_TOKEN,
        },
      });

      await consul.kv.set('env/HELLO_MESSAGE', 'Hello from TEST!');

      expect(appController.getConsulHello()).not.toBe('Hello from TEST!');

      await new Promise((resolve) => setTimeout(resolve, 1500));

      expect(appController.getConsulHello()).toBe('Hello from TEST!');
    });
  });
});



Enter fullscreen mode Exit fullscreen mode

b) Run tests



npm run test


Enter fullscreen mode Exit fullscreen mode

Image description

Please leave your appreciation by commenting on this post!

It takes one minute and is worth it for your career.

Okay, let's go.

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay