Set up a Node.js project with TypeScript and Biome

Photo by Soliman Cifuentes / Unsplash
Photo by Soliman Cifuentes / Unsplash

Biome is a fast code formatter and a performant code linter developers use to save time spent on linting and formatting their code.

Here are some advantages to using Biome:

  • It is faster at linting and formatting code than other tools.
  • It is easy to set up and use it with zero configuration.
  • The conciseness of the linting and formatting errors.
  • The ability to fix automatically many errors found in your code.
  • The integration with majors code editor and IDE.

This post will show you how to set up Node.js with TypeScript and use Biome as our code linter and formatter.

Prerequisites

To follow this tutorial, make sure you have the following tools installed on your computer.

Biome can work with Node.js 14 or higher, but we set the starting to 18 because building Node.js applications with a recent and maintained version is suitable.

If you still use a Node.js version < 18, you can follow this tutorial, but I recommend upgrading your Node.js.

Set up the Node.js project

Let's create a folder to hold the code and initialize a Node.js project. Open the terminal and run the following command:


mkdir node-ts-starter

cd node-ts-starter

yarn init

The last command initializes a new Node.js project; you will be asked to answer some questions about your project. After the execution, a "package.json" file is created.

The Node.js configuration project file.
The Node.js configuration project file.

Add TypeScript to the project.

Run the commands below to install the latest version of TypeScript:


yarn add typescript
yarn add -D @types/node

The second line installs the definitions of the types of Node.js native modules such as http, fs, path, cluster, etc.

Let's configure TypeScript by generating a file named "tsconfig.json" containing settings to perform type checks and transpile the code to JavaScript.

Run the command below to generate the TypeScript configuration file:


yarn tsc --init

You can customize the "tsconfig.json" as you want. I will provide the one I use on all my projects in the project repository at the end.

Run the Node.js project

Create an "src" folder, then create a file named "index.ts". Feel free to use your favorite editor or IDE.


mkdir src
touch src/index.ts

Open the file "index.ts" and paste the code below:


const addition = (a: number, b: number): number => {
  return a + b;
};

const number1: number = 5;
const number2: number = 10;
const result: number = addition(number1, number2);

console.log('The result is %d', result);

To run this code, we must install TypeScript runtimes such as TS Node, Tsx, Jiti, or Sucrase. Here is a comparison of features between major TypeScript runtimes.

We will combine TS Node and Nodemon to re-run the project on file change; run the commands below to these two packages.


yarn add -D ts-node nodemon

Run the command below to execute the file:


nodemon --watch "*.ts" --exec "ts-node" ./src/index.ts

This watches every change on all files having the .ts extension, then uses ts-node to run the project using the file ./src/index.ts as the entry point.

We get the following output:

Run a TypeScript project with TS Node.
Run a TypeScript project with TS Node.

Install Biome in Node.js

Run the command below to install the latest version of the package


yarn add --dev --exact @biomejs/biome

Biome maintainers recommend using a fixed version to avoid unexpected behavior.

Let's initialize the Biome configuration file by running the command below:


yarn biome init

The above command generates a file "biome.json" with a default configuration:


{
	"$schema": "https://biomejs.dev/schemas/1.7.0/schema.json",
	"organizeImports": {
		"enabled": true
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	}
}

The property "organizeImports" is enabled and thus will automatically sort and group modules and file imports.

The property "linter" is enabled and uses a set of predefined rules recommended to lint your code. Check out the recommended rules.

Configure Linter with Biome

The biome linter is enabled by default with predefined rules, but you can customize it by adding new rules or disabling some predefined rules.

Biome has 218 rules grouped into eight following categories:

  • Accessibility: check the errors related to accessibility.
  • Complexity: check complex code that can be simplified.
  • Correctness: detect code that is guaranteed to be incorrect or useless.
  • Performance: detect code that could be written to run faster or could be more efficient.
  • Security: detect code that could represent potential security flaws.
  • Style: enforce a consistent and idiomatic way of writing your code.
  • Suspicious: detect code that is likely to be incorrect or useless.
  • Nursery: a set of rules that is still under development and unstable. They are promoted to one of the seven groups once they become stable.

160 rules are enabled by default, and you must manually enable or disable new rules to adjust the linter for your project.

Configure Formatter with Biome

Although the formatting rules aren't defined in the Biome configuration file, it is enabled by default and will format your code following the default rules defined, which is the following code:


{
  "formatter": {
    "enabled": true,
    "formatWithErrors": false,
    "ignore": [],
    "attributePosition": "auto",
    "indentStyle": "tab",
    "indentWidth": 2,
    "lineEnding": "lf",
    "lineWidth": 80
  },
  "javascript": {
    "formatter": {
      "arrowParentheses":"always",
      "bracketSameLine": false,
      "bracketSpacing": true,
      "jsxQuoteStyle": "double",
      "quoteProperties": "asNeeded",
      "semicolons": "always",
      "trailingComma": "all"
    }
  },
  "json": {
    "formatter": {
      "trailingCommas": "none"
    }
  }
}

The "formatter" property is applied to any files marked in the project, irrespective of the supported programming languages.

The properties "javascript" and "json" define formatter options for these related languages.

You can now edit them to suit your code formatting preferences.

Let's say we want to define the following rules:

  • For all files, the line width is 100.
  • For JavaScript files, use a single quote instead of the double one.
  • For JSON files, use a space indentation instead of a tab.

Below is the Biome formatter configuration to define the above rules:


{
  "formatter": {
		"enabled": true,
		"formatWithErrors": false,
		"ignore": [],
		"indentStyle": "tab",
		"lineEnding": "lf",
		"lineWidth": 100
	},
	"javascript": {
		"formatter": {
			"quoteStyle": "single"
		}
	},
	"json": {
		"formatter": {
			"indentStyle": "space"
		}
	}
}

When a formatter rule is not defined, Biome will use the default value if it exists. For example, the JavaScript formatter has the rule "semicolons" with the default value of "all". It helps keep a light configuration file.

Lint files locally with Biome

The complete complete configuration is the following:


{
	"$schema": "https://biomejs.dev/schemas/1.7.0/schema.json",
	"vcs": {
		"clientKind": "git",
		"enabled": true,
		"useIgnoreFile": true
	},
	"organizeImports": {
		"enabled": true
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	},
	"formatter": {
		"enabled": true,
		"formatWithErrors": false,
		"ignore": [],
		"indentStyle": "tab",
		"lineEnding": "lf",
		"lineWidth": 100
	},
	"javascript": {
		"formatter": {
			"quoteStyle": "single"
		}
	},
	"json": {
		"formatter": {
			"indentStyle": "space"
		}
	}
}

Let's create a new file, "src/lint.ts" with the following code:


const names = ['Eric', 'Fabrice', 'Pavel', 'Sanix', 'Ange']

names.forEach((name) => {
  console.log('Hello ' + name + '!');
});

Run the following command to lint files in the "src" folders:


yarm biome lint ./src

We get the following output.

Lint the project files with Biome.
Lint the project files with Biome.

Biome detected two errors in the file "src/lint.ts":

  1. It recommends using for...of instead for...each
  2. It recommends using template literal instead of string concatenation; Biome can automatically fix it.

To lint files in the "src" folders and fix safe errors, run the following command:


yarn biome lint --apply ./src

We get the following output:

Lint the project files and apply a safe fix with Biome.
Lint the project files and apply a safe fix with Biome.

Biome still found two errors, although the second one is fixable; it is because the fix is marked unsafe and requires user verification.

If the fix suggested by Biome looks good to you, run the following command to apply the unsafe fix:


yarn biome lint --apply-unsafe ./src

We get the following output:

Lint the project files and apply an unsafe fix with Biome.
Lint the project files and apply an unsafe fix with Biome.

Only one error remains, and we must fix it manually.


const names = ['Eric', 'Fabrice', 'Pavel', 'Sanix', 'Ange']

for (const name of names) {
	console.log(`Hello ${name}!`);
}

Re-run un the command to lint the files, and you will get the following output:

No error was detected after linting the project with Biome.
No error was detected after linting the project with Biome.

Format files locally with Biome

To format files in the "src" folders, run the following command:


yarn biome format ./src

We get the following output.

Format the project files with Biome.
Format the project files with Biome.

Biome detected two errors in the file "src/index.ts" and "src/lint.ts":

To format files in the "src" folders and fix errors, run the following command:


yarn biome format --write ./src

We get the following output.

No error was detected after formatting the project with Biome.
No error was detected after formatting the project with Biome.

Lint and Format files locally with Biome

Biome provides a single command to perform the linting and formatting of the project; additionally, it organizes file imports.

Run the command below:


yarn biome check .

The "." means to use the current folder (project root) as the entry directory.

If you want to apply fixes after the lint and format, run the command below:


yarn biome check --apply .

Lint and format on Git commit with Biome

Before committing staged files in the Git history, you can lint them using Biome to prevent committing files from not matching the linting and formatting rules.

To do that, we will use Husky, which is a popular hook manager; let's install and initialize it:


yarn add -D husky

yarn husky init

yarn install

The yarn husky init command will:

  1. Add a "prepare" script in the package.json that will be executed every time you run yarn install.
  2. Create a folder ".husky" with a file related to the pre-commit hook.

Husky doesn't hide unstaged changes and cannot provide the list of staged files. We must use a tool that does that for us, like lint-staged; let's install it"


yarn add -D lint-staged

We want to run lint-staged on the pre-commit hook, so replace the content of the file ".husky/pre-commit" with the content below:


lint-staged

Update the "package.json" to add the following lint-staged configuration:


"lint-staged": {
    "*": [
      "biome check --apply --no-errors-on-unmatched --files-ignore-unknown=true"
    ]
}

Edit some files and make a Git commit to see the result:

Run a Biome check before a Git commit.
Run a Biome check before a Git commit.

Use Biome in a CI pipeline with GitHub Actions

Biome has a GitHub Action in the marketplace, allowing you to perform the lint and formatting in your CI pipeline.

In your project root directory, create a directory ".github/workflows", then create a file "build.yml"


mkdir -p .github/workflows

touch .github/workflows/build.yml

Open the "build.yml" file and the content below:


name: Build Project

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Biome
        uses: biomejs/setup-biome@v2
        with:
          version: latest

      - name: Run Biome
        run: biome ci .

        

Make a commit, push, and create a Pull request; you will see the GitHub Actions running.

The GitHub Actions workflow running in the Pull Request.
The GitHub Actions workflow running in the Pull Request.

Once the GitHub Actions workflow finishes the execution, click on details to see the job related to Biome.

The Biome check failed in the GitHub Actions workflow.
The Biome check failed in the GitHub Actions workflow.

The build failed because Biome found some linting and formatting errors in my project.

If you wonder why the pre-commit kook we set up didn't catch these errors, I added the flag –-no-verify to skip it.


git commit -m "my awesome feature" --no-verify

Let's fix it, commit to it, and push again. After the CI pipeline execution, we get a green light 🎉

The Biome check succeeded in the GitHub Actions workflow.
The Biome check succeeded in the GitHub Actions workflow.

Wrap Up

Biome makes it easy to format your code; it is simple to configure and customize depending on your needs by adding/removing new rules.

This tutorial taught us how to install Biome, configure it to lint, and format a Node.js project.

We also configured a Git hook to run Biome before every commit. Finally, we saw how to run Biome in a Continuous Integration pipeline.

Biome also offers plugins for VS Code and JetBrains IDE to enhance the experience of working with Biome locally.

Starting version 1.7, which we used in this tutorial, you can now migrate from Eslint and Prettier to Biome. Read my tutorial, which I will guide you through step by step.

Check out the Biome official documentation to go further into using it.

You can find the code source on the GitHub repository.

Follow me on Twitter or subscribe to my newsletter to avoid missing the upcoming posts and the tips and tricks I occasionally share.