Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save leandroluk/5e91f233916ce4a4b02f87e1c7a8a976 to your computer and use it in GitHub Desktop.
Save leandroluk/5e91f233916ce4a4b02f87e1c7a8a976 to your computer and use it in GitHub Desktop.

How to start a new backend NodeJS + Typecript project

  1. Commit chore: configuring vscode

    • create an repository on the git.

    • create file .gitignore:

      node_modules
      yarn*
      .tmp
    • create .vscode/settings.json with our settings

      {
      // editor
      "editor.formatOnPaste": true,
      "editor.formatOnSave": true,
      "editor.formatOnType": true,
      "editor.detectIndentation": false,
      "editor.tabSize": 2,
      "editor.insertSpaces": true,
      "editor.codeActionsOnSave": {
        "source.fixAll": "explicit",
        "source.organizeImports": "explicit"
      },
      "editor.rulers": [
        80,
        100,
        120
      ],
      // files	
      "files.encoding": "utf8",
      "files.eol": "\n",
      "files.insertFinalNewline": false,
      "files.trimFinalNewlines": true,
      "files.associations": {
        ".env*": "shellscript",
      },
      // docker
      "[dockercompose]": {
        "editor.formatOnSave": false
      },
      // git
      "git.inputValidation": false,
      // jestrunner
      "jestrunner.runOptions": [
        "--testTimeout=999999"
      ],
      // javascript
      "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
      "javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
      "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
      "javascript.format.insertSpaceBeforeFunctionParenthesis": false,
      "javascript.preferences.importModuleSpecifier": "shortest",
      "javascript.preferences.quoteStyle": "single",
      // typescript
      "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
      "typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
      "typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
      "typescript.format.insertSpaceBeforeFunctionParenthesis": false,
      "typescript.preferences.importModuleSpecifier": "shortest",
      "typescript.preferences.quoteStyle": "single",
      // xml
      "xml.format.enabled": false
      }
    • create .vscode/extensions.json with our prefer extensions

      {
        "recommendations": [
          // to run jest tests clicking directly on file
          "firsttris.vscode-jest-runner",
          // beautyfull icons
          "vscode-icons-team.vscode-icons",
          // eslint formatter
          "dbaeumer.vscode-eslint",
          // sonarlint codesmell check
          "SonarSource.sonarlint-vscode",
          // to create folder barrels (index.ts files)
          "mikerhyssmith.ts-barrelr"
        ]
      }
    • create the project using the command npm init -y and change the file like this:

      {
        "private": true,
        "name": "my-project-name",
        "displayName": "My project name",
        "description": "A simple description about the project",
        "version": "0.1.0",
        "homepage": "PROJECT_GIT_REPOSITORY_URL",
        "author": {
          "name": "You own name",
          "email": "you@email.com",
          "url": "https://www.linkedin.com/in/MY_LINKEDIN_PAGE_URL"
        },
      }
  2. Commit chore: configuring git & lefthook

    • install deps

      npm add -D -E @commitlint/cli @commitlint/config-conventional commitlint cz-conventional-changelog lefthook

      lefthook will create a file in root directory named lefthook.yml

    • change the package.json file adding this property:

      {
        "commitlint": {
          "extends": [
            "@commitlint/config-conventional"
          ]
        }
      }
    • update the file lefthook.yml with this code

      # https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md
      
      commit-msg:
        commands:
          commitlint:
            run: npx commitlint --edit # https://commitlint.js.org
  3. Commit chore: configuring typescript

    • run the command npx gts init.

    • remove all scripts existing in package.json

    • install other deps to ensure development process

      npm add -D -E dotenv tsconfig-paths
    • change the tsconfig.json to include instaled deps and configure absolute paths

      {
        "extends": "./node_modules/gts/tsconfig-google.json",
        "compilerOptions": {
          "rootDir": ".",
          "declaration": false,
          "esModuleInterop": true,
          "resolveJsonModule": true,
          "emitDecoratorMetadata": true,
          "experimentalDecorators": true,
          "strictPropertyInitialization": false,
          "target": "es5",
          "module": "commonjs",
          "strict": true,
          "baseUrl": "./",
          "paths": {
            "#/*": [
              "./src/*"
            ]
          },
        },
        "include": [
          "**/*.ts",
          "**/*.d.ts"
        ],
        "exclude": [
          "node_modules"
        ],
        "ts-node": {
          "files": true,
          "require": [
            "tsconfig-paths/register",
            "dotenv-expand/config"
          ]
        }
      }

      Note: using this tsconfig.json configuration, you can import ".env" files automatically and use absolute paths like #/domain/usecase/name-of-usecase.ts

  4. Commit chore: ensuring gts (eslint), prettier and configure lint-staged

    • install deps

      npm add -D -E lint-staged
    • change .eslintignore like this

      .tmp/*
      /node_modules/*
      
    • change .prettierrc.js to extend print width:

      module.exports = {
        ...require('gts/.prettierrc.json'),
        printWidth: 120,
      }
    • change the name of file .eslintrc.json into .eslintrc.js and change to this code

      module.exports = {
        extends: ['./node_modules/gts/'],
        rules: {
          // @typescript-eslint
          '@typescript-eslint/no-namespace': 'off',
          '@typescript-eslint/no-explicit-any': 'off',
          '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
          '@typescript-eslint/consistent-type-imports': [
            'error',
            {
              fixStyle: 'inline-type-imports',
            },
          ],
          '@typescript-eslint/no-unused-vars': [
            'error',
            {
              argsIgnorePattern: '^_',
              varsIgnorePattern: '^_',
              caughtErrorsIgnorePattern: '^_',
            },
          ],
          // import
          'import/no-duplicates': 'off',
          // node
          'node/no-extraneous-import': 'off',
          // prettier/prettier
          'prettier/prettier': [
            'error',
            {
              endOfLine: 'auto',
            },
          ],
        },
        settings: {
          node: {
            allowModules: [],
          },
        },
      };
    • change the package.json file adding this property

      {
        "lint-staged": {
          "*.{ts,tsx}": [
            "eslint --fix",
            "git add"
          ]
        },
      }
    • add script to package.json

      "scripts": {
        "lint": "eslint . --fix",
    • change the file lefthook.yml adding this code

      pre-commit:
        parallel: true
        commands:
          lint-staged:
            run: npx lint-staged # https://github.com/lint-staged/lint-staged
  5. Commit chore: configuring tests

    • install deps

      npm add -D -E @jest/types @types/jest @types/supertest jest jest-environment-node supertest ts-jest ts-node
    • create jest.config.ts

      import {type Config} from '@jest/types';
      const config: Config.InitialOptions = {
        preset: 'ts-jest',
        roots: ['<rootDir>/src', '<rootDir>/mocks', '<rootDir>/tests'],
        setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
        coverageReporters: ['json-summary', 'text', 'lcov'],
        coverageDirectory: '.tmp/coverage',
        collectCoverageFrom: [
          '<rootDir>/src/**/*.ts',
          '!<rootDir>/src/**/index.ts',
          '!<rootDir>/src/.d.ts',
          '!<rootDir>/src/logger.ts',
          '!<rootDir>/src/vars.ts',
        ],
        testEnvironment: 'node',
        testMatch: ['**/*.spec.ts', '**/*.test.ts'],
        testPathIgnorePatterns: ['/node_modules/'],
        moduleNameMapper: {
          'package.json': '<rootDir>/package.json',
          'mocks/(.*)': '<rootDir>/mocks/$1',
          '[#]/(.*)': '<rootDir>/src/$1',
          'tests/(.*)': '<rootDir>/tests/$1',
        },
        coverageThreshold: {
          global: {
            branches: 100,
            functions: 100,
            lines: 100,
            statements: 100,
          },
        },
      };
      export default config;
    • create jest.unit.config.ts to run only unit tests

      import config from './jest.config';
      config.collectCoverageFrom = ['!<rootDir>/src/main/**/*.ts'];
      config.testMatch = ['**/*.spec.ts'];
      export default config;
    • create jest.integration.config.ts to run only integration tests

      import config from './jest.config';
      config.collectCoverageFrom = ['<rootDir>/src/main/**/*.ts'];
      config.testMatch = ['**/*.test.ts'];
      export default config;
    • add scripts to package.json

      "scripts": {
        "test": "jest --passWithNoTests --runInBand --detectOpenHandles --silent --noStackTrace",
        "test:v": "jest --passWithNoTests --runInBand --detectOpenHandles --verbose",
        "test:w": "npm run test -- --watch",
        "test:u": "npm run test -- -c jest.unit.config.ts",
        "test:i": "npm run test -- -c jest.integration.config.ts",
        "test:ci": "npm run test -- --coverage",
        "test:ci:u": "npm run test:u -- --coverage",
        "test:ci:i": "npm run test:i -- --coverage",
        "test:staged": "npm test -- --findRelatedTests"
      }
    • add tests/setup.ts file to add mocks and others setup configurations

      // this is an example
      global.console = {
        log: jest.fn(),
        error: jest.fn(),
        warn: jest.fn(),
      } as unknown as Console;
    • create the file mocks/.gitkeep to persist the mocks folder

    • change the src/index.ts to tests work:

      console.log(1);
    • run the command npm run test to see if works

  6. Commit chore: configuring build

    • add deps

      npm add -D -E rimraf webpack webpack-cli ts-loader css-loader terser-webpack-plugin tsconfig-paths-webpack-plugin
    • add this script to build project:

      "script": {
        "build": "rimraf .tmp && webpack --config webpack.config.ts",
      }
    • create the webpack.config.ts file in root dir like this:

      import path from 'path';
      import TerserPlugin from 'terser-webpack-plugin';
      import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
      import {type Configuration} from 'webpack';
      const config: Configuration = {
        target: 'node',
        mode: 'production',
        entry: './src/index.ts',
        output: {
          filename: 'index.js',
          path: path.resolve(__dirname, '.tmp/dist'),
        },
        resolve: {
          extensions: ['.ts', '.js'],
          plugins: [new TsconfigPathsPlugin()],
        },
        node: {__dirname: false, __filename: false},
        externalsType: 'commonjs',
        module: {
          rules: [
            {test: /\.(sa|sc|c)ss$/, use: ['style-loader', 'css-loader', 'sass-loader']},
            {test: /\.ts$/, use: 'ts-loader', exclude: /node_modules/},
          ],
        },
        optimization: {
          minimize: true,
          minimizer: [
            new TerserPlugin({
              extractComments: false,
              terserOptions: {compress: true, mangle: false},
            }),
          ],
        },
      };
      export default config;
    • run the command npm run build to see if works. (the project will be compiled and saved in folder .tmp/dist)

    Aditional steps to use swagger-ui-express

    The lib swagger-ui-express can be used to provide a swagger user interface to document own api. To configure with webpack do these steps:

    • install the aditional dependencies:

      npm add -D copy-webpack-plugin 
    • change the webpack.config.ts to adding the configurations to import swagger-ui-express dependencies when build the application

      // webpack.config.ts
      import CopyWebpackPlugin from 'copy-webpack-plugin';
      
      const config: Configuration = {
        // ...
        plugins: [
          new CopyWebpackPlugin({
            patterns: [
              './node_modules/swagger-ui-dist/swagger-ui.css',
              './node_modules/swagger-ui-dist/swagger-ui-bundle.js',
              './node_modules/swagger-ui-dist/swagger-ui-standalone-preset.js',
              './node_modules/swagger-ui-dist/favicon-16x16.png',
              './node_modules/swagger-ui-dist/favicon-32x32.png',
            ],
          }),
        ],
      };
      export default config;

  7. Commit chore: configuring docker

    • create .dockerignore

      .tmp
      .vscode
      .scannerwork
      node_modules
    • create Dockerfile

        FROM node:16-bullseye-slim AS builder
        WORKDIR /app
        COPY . .
        RUN \
          npm install --frozen-lockfile && \
          npm build
      
        FROM node:16-bullseye-slim AS runner
        WORKDIR /app
        ENV \
          # APP
          NODE_ENV="development" \
          PORT="3000"
        # other env vars goes here
        COPY --from=builder /app/.tmp/dist/ /app/
        EXPOSE ${PORT}
        CMD node index.js

    note: the next steps in this section you need only if will use a database (this is a example)

    • create db.sql to build database structure

      CREATE TABLE example (
        "id" BIGINT NOT NULL SERIAL,
        ...
      );
      /* migrations and seeds goes here*/

    note in this example i will use postgres as database

    • create docker-compose.yml overitten {{REPOSITORY_NAME}} with our main repository name

      version: '3'
      networks:
        {{REPOSITORY_NAME}}:
          name: {{REPOSITORY_NAME}}
      services:
        {{REPOSITORY_NAME}}-service-name: 
          # service properties

      That docker-compose.yml is a simple example. Below you can see some commonly used services

      PostgresSQL
      {{REPOSITORY_NAME}}-postgres:
        image: postgres
        hostname: postgres
        container_name: {{REPOSITORY_NAME}}-postgres
        ports: [ '5432:5432' ]
        volumes: [ './db.sql:/docker-entrypoint-initdb.d/db.sql' ] # if uses an db script file (see image docs)
        networks: [ '{{REPOSITORY_NAME}}' ]
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        healthcheck:
          test: ['CMD-SHELL', 'pg_isready -U postgres']
          interval: 10s
          timeout: 5s
          start_period: 10s
      MySQL
      {{REPOSITORY_NAME}}-mysql:
        image: mysql
        hostname: mysql
        container_name: {{REPOSITORY_NAME}}-mysql
        ports: [ '3306:3306' ]
        volumes: [ './db.sql:/docker-entrypoint-initdb.d/db.sql' ] # if uses an db script file (see image docs)
        networks: [ '{{REPOSITORY_NAME}}' ]
        environment: 
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: mysql
          MYSQL_USER: mysql
          MYSQL_PASSWORD: mysql
        healthcheck:
          test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-proot" ]
          timeout: 10s
          retries: 10
          start_period: 10s
      MongoDB
      {{REPOSITORY_NAME}}-mongo:
        image: mongo
        hostname: mongo
        container_name: {{REPOSITORY_NAME}}-mongo
        ports: [ '27017:27017' ]
        volumes: [ './db.js:/docker-entrypoint-initdb.d/db.js' ] # if uses an db script file (see image docs)
        networks: [ '{{REPOSITORY_NAME}}' ]
        environment: 
          MONGO_INITDB_ROOT_USERNAME: mongo
          MONGO_INITDB_ROOT_PASSWORD: mongo
          MONGO_INITDB_DATABASE: mongo
        healthcheck:
          test: ["CMD", "mongo", "--eval", "db.stats()"]
          interval: 10s
          timeout: 5s
          retries: 3
      MongoDB (Atlas version)
      {{REPOSITORY_NAME}}-mongo-atlas:
        image: mongodb/atlas
        hostname: mongo-atlas
        container_name: {{REPOSITORY_NAME}}-mongo-atlas
        ports: [ "${MONGO_PORT}:${MONGO_PORT}" ]
        networks: [ "{{REPOSITORY_NAME}}" ]
        privileged: true
        command: "bash -c '\
          atlas deployments setup \
            --type local \
            --bindIpAll \
            --port ${MONGO_PORT} \
            --username ${MONGO_USERNAME} \
            --password ${MONGO_PASSWORD} \
            --force && tail -f /dev/null'"
        healthcheck:
          test: [ "CMD", "mongosh", "--host", "localhost", "--port", "${MONGO_PORT}", "-u", "${MONGO_USERNAME}", "-p", "${MONGO_PASSWORD}", "--eval", "db.stats()" ]
          interval: 10s
          timeout: 5s
          retries: 20
      Redis
      {{REPOSITORY_NAME}}-redis:
        image: redis/redis-stack-server
        hostname: redis
        container_name: {{REPOSITORY_NAME}}-redis
        ports: [ '6379:6379' ]
        networks: [ '{{REPOSITORY_NAME}}' ]
        environment:
          # by default the redis username is "default", so with that we set the password to "default" to
          REDIS_ARGS: "--requirepass default"
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 3s
          retries: 3                
      Sonarqube
      {{REPOSITORY_NAME}}-sonarqube:
        image: sonarqube
        hostname: sonarqube
        container_name: {{REPOSITORY_NAME}}-sonarqube
        ports: [ '9000:9000' ]
        networks: [ '{{REPOSITORY_NAME}}' ]
        healthcheck:
          test: curl --fail http://localhost:9000 || exit 1
          interval: 30s
          timeout: 10s
          retries: 5
      RabbitMQ
      {{REPOSITORY_NAME}}-rabbitmq:
        image: rabbitmq:management
        hostname: rabbitmq
        container_name: {{REPOSITORY_NAME}}-rabbitmq
        ports: [ '5672:5672', '15672:15672' ]
        networks: [ '{{REPOSITORY_NAME}}' ]
        environment:
          RABBITMQ_DEFAULT_USER: rabbitmq
          RABBITMQ_DEFAULT_PASS: rabbitmq          
        healthcheck:
          test: ["CMD", "rabbitmq-diagnostics", "ping"]
          interval: 30s
          timeout: 10s
          retries: 5
      Liquibase (with Postgres)
      {{REPOSITORY_NAME}}-liquibase:
          container_name: {{REPOSITORY_NAME}}-liquibase
          hostname: liquibase
          platform: linux/amd64
          image: liquibase/liquibase
          networks: [ '{{REPOSITORY_NAME}}' ]
          depends_on: 
            {{REPOSITORY_NAME}}-postgres:
              condition: service_healthy
          # you need a file called "changelog.xml" in same directory of docker-compose
          volumes: [ './changelog.xml:/liquibase/changelog/changelog.xml' ]
          command: "bash -c '\
            liquibase \
              --url=jdbc:postgresql://postgresql:${POSTGRESQL_PORT}/${POSTGRESQL_DATABASE} \
              --searchPath=/liquibase/changelog \
              --changeLogFile=changelog.xml \
              --username=${POSTGRESQL_USERNAME} \
              --password=${POSTGRESQL_PASSWORD} \
              --databaseChangeLogTableName=_changelog \
              --databaseChangeLogLockTableName=_changelog_lock \
              updateToTag ${LIQUIBASE_TAG}'"

      Here is a example with the changelog.xml file:

      <?xml version="1.0" encoding="UTF-8"?>
      <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                         xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
                         xmlns:pro="http://www.liquibase.org/xml/ns/pro"
                         xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
                                             http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
                                             http://www.liquibase.org/xml/ns/dbchangelog-ext
                                             http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
                                             http://www.liquibase.org/xml/ns/pro
                                             http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd">
        <changeSet id="123/change-set-id" author="developer@email.com">
          <sql>
            CREATE TABLE "users" (
              "id"              UUID                         NOT NULL  DEFAULT GEN_RANDOM_UUID(),
              "created_at"      TIMESTAMP(3) WITH TIME ZONE  NOT NULL  DEFAULT CURRENT_TIMESTAMP(3),
              "last_updated_at" TIMESTAMP(3) WITH TIME ZONE  NOT NULL  DEFAULT CURRENT_TIMESTAMP(3),
              "removed_at"      TIMESTAMP(3) WITH TIME ZONE      NULL,
              "name"            VARCHAR(100)                     NULL,
              "email"           VARCHAR(200)                 NOT NULL,
              --
              PRIMARY KEY ("id")
            );
          </sql>
      
          <rollback>
            DROP TABLE IF EXISTS "users";
          </rollback>
        </changeSet>
      
        <changeSet id="123/tag" author="developer@email.com">
          <tagDatabase tag="123" />
        </changeSet>
      </databaseChangeLog>
      Keycloak
      {{REPOSITORY_NAME}}-keycloak:
        image: quay.io/keycloak/keycloak
        hostname: keycloak
        container_name: api-keycloak
        ports: [ '${KEYCLOAK_PORT}:8080' ]
        networks: [ '{{REPOSITORY_NAME}}' ]
        environment:
          KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN_USERNAME}
          KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
        # uncomment this only if you have a realm.json file
        # volumes: [ './realm.json:/opt/keycloak/data/import/realm.json' ]
        command: start-dev --import-realm
        healthcheck:
          test: curl --fail http://localhost:8080 || exit 1
          interval: 30s
          timeout: 10s
          retries: 3
      • Note: to generate realm.json you can access the admin panel, create any realm, access the "realm settings" and click in export.

      • Alert: Before export for use with this script you need to edit the json file removing an property called "authorizationSettings" or the script cannot work. You can see more details about this error here

      • Tips:

        1. Try create the client used for develop before export the realm json file, this will help you when develop
        2. You can edit the realm json file setting a hardcoded secret value on property clients[i].secret. Find the created client on previous tip.
        3. You can add users in the realm json file. To do this, add objects to property users like this example:
          {
            "id": "john.doe@email.com",
            "username": "john.doe@email.com",
            "email": "john.doe@email.com",
            "emailVerified": true,
            "enabled": true,
            "firstName": "John",
            "lastName": "Doe",
            "credentials": [{"type": "password","value": "password"}],
            // you can get the "default-role-name" in property 
            // "defaultRole.name" of realm json file
            "realmRoles": ["{{default-role-name}}"] 
          },

      Note: All service scripts here are created by AI solutions like Gemini or ChatGPT.
      If you need any other service, please see the image docs on Docker Hub or try generate the markup code with AI's.

    • add scripts to package.json

      "scripts": {
        "compose": "docker-compose down --remove-orphans && docker-compose up --build --force-recreate",
        "compose:d": "npm run compose -- -d",
      }
  8. Commit chore: configuring debugger:

    • install dependencies to debug

      npm add -D -E nodemon ts-node
    • add scripts to package.json

      "scripts": {
        "dev": "nodemon --watch src --ext ts --exec \"ts-node src\""
      }
    • create file .vscode/launch.json

      {
        "version": "0.2.0",
        "configurations": [
          {
            "name": "dev",
            "request": "launch",
            "runtimeArgs": [ "run-script", "dev" ],
            "outputCapture": "std",
            "runtimeExecutable": "npm",
            "type": "node"
          }
        ],
        "compounds": []
      }
  9. Commit chore: configure sonarcube

    • Install the sonnar-scanner into you operation system.

      Windows
      1. The easy way to install is using the Chocolatey. Install the chocolatey using the site tutorial.

      2. Now open an terminal with admin privileges and run the command:

        choco install sonarqube-scanner.portable -y
      3. Next you can check the instalation using the command:

        sonnar-scanner -v
      Linux

      Depending of the distribution you may need to find the correct command. With Ubuntu as example we have:

      1. Run the command to download the cli:

        wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.0.2311-linux.zip
      2. Extract the download file with command:

        sudo mkdir /opt/sonar-scanner && 
        sudo unzip sonar-scanner-cli-4.6.0.2311-linux.zip -d /opt/sonar-scanner
      3. Add the sonnar-scanner binary paths into you environment vars. Normally you need edit the file ~/.bashrc, ~/.bash_profile or ~/.zshrc (if using ZSH) adding this line in the end of file:

        export PATH="/opt/sonar-scanner/bin:$PATH"

        Save the file and run the command source ${PATH_TO_CHANGED_FILE}.

      4. Now you can check the instalation using the command:

        sonnar-scanner -v
      MacOS

      The easy way is using the brew to install.

      1. Install the brew using the command:

        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
      2. Next, run the command:

        brew install sonar-scanner
      3. Now you can check the instalation using the command:

        sonnar-scanner -v

    • Add the sonarcube server to the docker-compose.yml. You can use the template in the session about Docker and Docker Compose and start it with the nodejs command npm run compose:d.

    • Access the sonarcube on the endpoint http://localhost:9000 and do login using the user=admin and password=admin. The sonarcube would request to change the password. Normally I change to password=password.

    • When login successful, access the endpoint http://localhost:9000/account/security and generate a token called "development" with "Global" type and without expiration time.

    • The sonarcube will generate a token like abc_def.... Now create a file .env.example with this content:

      # sonarcube
      SONARCUBE_HOST  = "http://localhost"
      SONARCUBE_PORT  = 9000
      SONARCUBE_TOKEN = ""

      Now duplicate the file .env.example with name .env and set the generated token into the SONARCUBE_TOKEN variable.

      Note: The file .env.example is used to maintain the default .env file format to configure the environment vars

    • Create the file .scripts/sonar.js with this code:

      require('dotenv').config();
      
      const {exec} = require('child_process');
      const packageJson = require('../package.json');
      
      const command = [
        "sonar-scanner",
        `-Dsonar.projectKey="${packageJson.name}"`,
        `-Dsonar.projectName="${packageJson.displayName}"`,
        `-Dsonar.projectVersion="${packageJson.version}"`,
        `-Dsonar.sources="./src"`,
        `-Dsonar.language="ts"`,
        `-Dsonar.working.directory="./.tmp/scannerwork"`,
        `-Dsonar.login="${process.env.SONARCUBE_TOKEN}"`
      ].join(" ");
      
      const childProcess = exec(command);
      childProcess.stdout.on('data', (data) => console.log(data));
      childProcess.on('close', (code) => process.exit(code));
    • Add an script to package.json file:

      {
        "scripts": {
          "sonar": "node ./.scripts/sonar.js"
        }
      }

      Note: this will enable to run the sonar-scanner into this project. You can access the project overview in the url http://localhost:9000/dashboard?id=${package.json#name}

    • Add the directory to .gitignore file like this:

      node_modules
      yarn*
      .tmp
      .scannerwork
      .env
      
  10. Start development of application.

    • Every change by each file you need make commit's in conventional-commit design structure

    • The ESLint config is an default config, you can change this if needed

    • If you have any question with this gist, call me on Linkedin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment