NestJS project setup with Biome linter and formatter

NestJS with Biome
NestJS with Biome

Introduction

Biome is a fast, modern toolchain for linting and formatting that serves as a powerful alternative to ESLint and Prettier. If you're building a NestJS backend and want to reduce tooling complexity while improving performance, Biome is an excellent choice.

This guide walks you through the complete setup process: removing traditional linting and formatting tools, installing Biome, and configuring it to work seamlessly with your NestJS project. By the end, you'll have a lean, performant development environment.

Prerequisites

  1. Node/PNPM installed on the server

Step-by-step

  1. Generate a new NestJS project:

    bash
    npx @nestjs/cli@latest new nestjs-biome
    
  2. Remove eslint and prettier dependencies:

    bash
    pnpm remove globals @eslint/eslintrc @eslint/js eslint eslint-config-prettier eslint-plugin-prettier typescript-eslint prettier
    
    rm .prettierrc eslint.config.mjs
    
  3. Install Biome:

    bash
    pnpm add -D -E @biomejs/biome
    
  4. Create a biome.config.mjs file in the root of your project with the following content:

    javascript
    {
      "$schema": "https://biomejs.dev/schemas/latest/schema.json",
      "vcs": {
        "enabled": false,
        "clientKind": "git",
        "useIgnoreFile": true
      },
      "files": {
        "ignoreUnknown": true,
        "includes": [
          "**",
          "!dist",
          "!node_modules",
          "!.pnpm-store",
          "!coverage",
          "!.git"
        ]
      },
      "formatter": {
        "enabled": true,
        "indentStyle": "space"
      },
      "linter": {
        "enabled": true,
        "rules": {
          "recommended": true,
          "style": {
            "useImportType": "off"
          }
        }
      },
      "javascript": {
        "formatter": {
          "quoteStyle": "single",
          "semicolons": "always"
        },
        "parser": {
          "unsafeParameterDecoratorsEnabled": true
        }
      }
    }
    
  5. Update format and lint scripts from package.json:

    json
    "scripts": {
      "format": "biome format --write .",
      "lint": "biome check --write .",
    }
    
  6. Update Jest configuration in package.json:

    json
    "jest": {
      "moduleFileExtensions": [
        "js",
        "json",
        "ts"
      ],
      "rootDir": "src",
      "testRegex": ".*\\.spec\\.ts$",
      "transform": {
        "^.+\\.(t|j)s$": "ts-jest"
      },
      "moduleNameMapper": {
        "^@/(.*)$": "<rootDir>/$1"
      },
      "coverageProvider": "v8",
      "collectCoverageFrom": [
        "**/*.(t|j)s",
        "!**/main.ts",
        "!**/*.module.ts",
        "!**/ormconfig.ts",
        "!**/*.entity.ts",
        "!**/*.input.ts",
        "!**/*.types.ts"
      ],
      "coverageDirectory": "../coverage",
      "testEnvironment": "node"
    }
    
  7. Update dependencies in package.json:

    bash
    npx npm-check-updates -u
    
  8. Update tsconfig.json:

    json
    {
      "compilerOptions": {
        "baseUrl": ".",
        "ignoreDeprecations": "6.0",
        "paths": {
          "@/*": ["src/*"]
        },
        "module": "nodenext",
        "moduleResolution": "nodenext",
        "resolvePackageJsonExports": true,
        "esModuleInterop": true,
        "isolatedModules": true,
        "declaration": true,
        "removeComments": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "target": "ES2023",
        "sourceMap": true,
        "rootDir": ".",
        "outDir": "./dist",
        "incremental": true,
        "types": ["node", "jest"],
        "skipLibCheck": true,
        "strictNullChecks": true,
        "forceConsistentCasingInFileNames": true,
        "noImplicitAny": true,
        "strictBindCallApply": true,
        "noFallthroughCasesInSwitch": true
      }
    }
    
  9. Update tsconfig.build.json:

    json
    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "rootDir": "./src",
        "types": ["node"],
        "tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
      },
      "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
    }
    
  10. Use type keyword for type-only imports

    typescript
    import { Test, type TestingModule } from '@nestjs/testing';
    import type { INestApplication } from '@nestjs/common';
    import { Test, type TestingModule } from '@nestjs/testing';
    import type { App } from 'supertest/types';
    
  11. Exclude constructor from code coverage

    typescript
    /* c8 ignore next 1 */
    constructor(private readonly appService: AppService) {}
    

Conclusion

You've successfully integrated Biome into your NestJS project! Your development environment is now faster and simpler, with Biome handling both linting and formatting in one cohesive tool. You can now run pnpm format to format your code and pnpm lint to check for code quality issues. Enjoy the streamlined workflow and continue building great things with NestJS.

If you found this useful, you can buy me a coffee! Thanks for the support!