codeWithYoha logo
Code with Yoha
HomeArticlesAboutContact
Biome

Exploring Biome: The Fast, All-in-One Toolchain for Modern Web Projects

CodeWithYoha
CodeWithYoha
17 min read
Exploring Biome: The Fast, All-in-One Toolchain for Modern Web Projects

Introduction

In the rapidly evolving landscape of web development, managing a project's toolchain can often feel like assembling a complex puzzle. Developers frequently juggle a multitude of separate tools: ESLint for linting, Prettier for formatting, Babel for transpilation, Webpack or Rollup for bundling, and Jest or Vitest for testing. While each tool excels in its niche, their combined configuration, dependency management, and performance overhead can introduce significant friction and slow down development cycles.

This is where Biome steps in. Biome is an ambitious, high-performance toolchain designed from the ground up to be an all-in-one solution for web projects. Built in Rust, it aims to consolidate formatting, linting, bundling, compiling, and testing into a single, cohesive, and incredibly fast platform. Currently, its formatter and linter are robust and production-ready, offering a compelling alternative to the traditional fragmented setup.

This comprehensive guide will dive deep into Biome, exploring its philosophy, features, usage, and how it can streamline your development workflow. Whether you're a seasoned developer or new to the ecosystem, understanding Biome can unlock new levels of efficiency and consistency in your projects.

Prerequisites

To follow along with this guide, you'll need:

  • Node.js and npm/Yarn/pnpm installed on your system.
  • A basic understanding of JavaScript or TypeScript.
  • Familiarity with command-line interfaces.
  • (Optional but recommended) A code editor like VS Code with the Biome extension.

What is Biome? The All-in-One Vision

Biome (formerly Rome) is developed by the creators of Prettier and aims to be a complete toolchain for web development. Its core philosophy revolves around simplification and performance. By unifying disparate tools, Biome eliminates the overhead of multiple configurations, reduces dependency bloat, and ensures consistent behavior across different aspects of your project.

Key aspects of Biome's vision and current capabilities:

  • Single Binary, Multiple Roles: Instead of installing and configuring five different packages, you install one: Biome. This single binary handles various tasks, reducing complexity.
  • Built in Rust: The choice of Rust as its foundational language is critical. Rust's focus on performance, memory safety, and concurrency allows Biome to execute tasks at lightning speed, significantly outperforming JavaScript-based alternatives.
  • Formatter: A highly opinionated, fast code formatter that rivals Prettier, ensuring consistent code style across your entire codebase.
  • Linter: A powerful linter with a comprehensive set of rules, designed to catch errors, enforce best practices, and improve code quality, similar to ESLint.
  • Future Ambitions: The long-term roadmap includes a bundler (like Webpack/Rollup), a compiler (like Babel/SWC), and a test runner (like Jest/Vitest), making it a truly holistic solution.

This unified approach not only simplifies setup but also provides a more coherent developer experience, as all tools share the same parsing logic and configuration schema.

Why Biome? The Problem with Current Toolchains

To truly appreciate Biome, it's essential to understand the challenges it seeks to address. The current state of JavaScript/TypeScript toolchains, while powerful, often presents several pain points:

  1. Configuration Sprawl: Each tool (ESLint, Prettier, Babel, Webpack, etc.) comes with its own configuration file (.eslintrc.js, .prettierrc.json, babel.config.js, webpack.config.js). Managing these can be tedious, error-prone, and lead to inconsistencies.
  2. Performance Overhead: Running multiple JavaScript-based tools sequentially or in parallel can be slow. Each tool has its own startup time, parsing logic, and execution overhead. For large projects, this can significantly impact CI/CD times and local development feedback loops.
  3. Dependency Hell: A typical project's node_modules directory can be enormous, filled with dozens or even hundreds of packages and their transitive dependencies for linting, formatting, and building alone. This increases installation times and potential security vulnerabilities.
  4. Inconsistent Parsing: Different tools might use different parsers (e.g., Babel's parser vs. ESLint's default parser), leading to subtle inconsistencies in how code is interpreted, which can cause unexpected errors or formatting glitches.
  5. Maintenance Burden: Keeping all these tools updated, resolving compatibility issues between them, and ensuring they work harmoniously requires ongoing effort.

Biome aims to resolve these issues by providing a single, performant binary that handles all these tasks with a unified configuration and a shared, robust Rust-powered parser. This significantly reduces the cognitive load and operational overhead for developers.

Getting Started with Biome

Integrating Biome into your project is straightforward. Let's walk through the basic steps.

Installation

You can install Biome as a development dependency in your project:

npm install --save-dev @biomejs/biome
# or
yarn add --dev @biomejs/biome
# or
pnpm add --save-dev @biomejs/biome

Alternatively, you can use npx to run Biome commands without global installation, which is convenient for one-off tasks or initial setup:

npx @biomejs/biome --help

Initializing Biome

After installation, the first step is often to initialize Biome in your project. This creates a biome.json configuration file in your project root.

npx @biomejs/biome init

This command will generate a biome.json file with sensible defaults, looking something like this:

{
  "extends": [],
  "files": {
    "ignore": [
      "node_modules",
      "dist"
    ]
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 80,
    "quoteStyle": "single",
    "jsxQuoteStyle": "double",
    "semicolons": "always"
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "suspicious": {
        "noDebugger": "error",
        "noDuplicateObjectKeys": "error"
      }
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "jsxQuoteStyle": "double"
    }
  }
}

This configuration file is your central hub for controlling Biome's behavior.

Basic Usage: Format and Lint

Once biome.json is set up, you can start using Biome's core functionalities.

Formatting your code:

To format all supported files in your project, run:

npx @biomejs/biome format .

To write the changes back to the files, use --write:

npx @biomejs/biome format --write .

Linting your code:

To check for linting errors and warnings:

npx @biomejs/biome lint .

To automatically fix fixable linting issues, use --apply or --apply-unsafe:

npx @biomejs/biome lint --apply .
# or for more aggressive fixes that might change runtime behavior
npx @biomejs/biome lint --apply-unsafe .

It's common practice to add these commands to your package.json scripts for easy access:

{
  "name": "my-biome-project",
  "version": "1.0.0",
  "devDependencies": {
    "@biomejs/biome": "latest"
  },
  "scripts": {
    "format": "biome format --write .",
    "lint": "biome lint .",
    "lint:fix": "biome lint --apply .",
    "check": "biome check --apply ."
  }
}

The biome check command is a convenient shortcut that runs both format and lint (with --apply by default) in one go, making it ideal for pre-commit hooks or CI/CD pipelines.

Biome as a Formatter

Biome's formatter is a strong contender to Prettier, offering comparable features with superior performance thanks to its Rust implementation. It's highly opinionated by default, which means less time spent debating code style and more time writing code.

Key Features and Configuration

Biome's formatter is designed for consistency. It supports JavaScript, TypeScript, JSX, TSX, and JSON files.

Common formatting options configurable in biome.json:

  • indentStyle: "space" or "tab"
  • indentWidth: Number of spaces for indentation.
  • lineWidth: Maximum line width before wrapping.
  • quoteStyle: "single" or "double" for string literals.
  • jsxQuoteStyle: "single" or "double" for JSX attribute values.
  • semicolons: "always" or "asNeeded".

Example: Formatting a messy file

Let's consider a JavaScript file named src/index.js:

// src/index.js (before formatting)
const   myFunction =  (  param1,param2 )=> {
const  result =param1+param2;console.log( `The result is: ${  result}` );return result;};

  class   MyClass  {constructor(name){this.name=name;}greet(){return  `Hello, ${this.name}!`;}}

const instance =new MyClass('Biome'); console.log(instance.greet());

const arr=[1,2,3,4,5];

  arr.forEach(item=>console.log(item));

After running npx @biomejs/biome format --write src/index.js with the default biome.json (single quotes, 2 spaces, 80 char line width):

// src/index.js (after formatting)
const myFunction = (param1, param2) => {
  const result = param1 + param2;
  console.log(`The result is: ${result}`);
  return result;
};

class MyClass {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return `Hello, ${this.name}!`;
  }
}

const instance = new MyClass('Biome');
console.log(instance.greet());

const arr = [1, 2, 3, 4, 5];

arr.forEach((item) => console.log(item));

Notice how Biome automatically corrected indentation, spacing, semicolon usage, and line breaks, ensuring a consistent and readable style.

Biome as a Linter

Biome's linter provides fast and intelligent code analysis to identify potential errors, enforce best practices, and improve code maintainability. It comes with a comprehensive set of built-in rules, many of which are inspired by popular ESLint rules.

Key Features and Configuration

  • Built-in Rules: Biome includes a robust collection of rules categorized into recommended, suspicious, correctness, security, complexity, performance, style, and a11y.
  • Performance: As with formatting, linting is incredibly fast due to its Rust implementation.
  • Automatic Fixes: Many linting rules come with automatic fixes, which can be applied using the --apply or --apply-unsafe flags.
  • Unified Parsing: The linter uses the same parser as the formatter, preventing inconsistencies.

Configuring linting rules is done in the linter section of biome.json:

{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true, 
      "complexity": {
        "noExtraBooleanCast": "error" 
      },
      "correctness": {
        "noUnusedVariables": "error", 
        "noConstantCondition": "warn"
      },
      "security": {
        "noDangerouslySetInnerHtml": "error"
      },
      "style": {
        "noVar": "error", 
        "useConst": "warn"
      }
    }
  }
}

Each rule can be configured with a severity level: "off", "warn", or "error".

Example: Linting with a specific rule

Consider a file src/app.js:

// src/app.js
var globalVar = 'I am a global variable'; // style/noVar rule violation

function calculate(a, b) {
  const unused = 10; // correctness/noUnusedVariables rule violation
  if (!!true) { // complexity/noExtraBooleanCast rule violation
    return a + b;
  }
  return 0;
}

console.log(calculate(5, 3));

Running npx @biomejs/biome lint src/app.js with the linter configuration above would yield output similar to:

src/app.js:2:1 ERROR   This `var` declaration should be an `const` or `let` declaration. (style/noVar)

src/app.js:5:9 WARNING   This variable is unused. (correctness/noUnusedVariables)

src/app.js:6:7 ERROR   Unnecessary double boolean cast. (complexity/noExtraBooleanCast)

Found 1 warning and 2 errors.

Now, if we run npx @biomejs/biome lint --apply src/app.js:

// src/app.js (after --apply)
const globalVar = 'I am a global variable';

function calculate(a, b) {
  const unused = 10; // This line might still be flagged as unused if the rule doesn't auto-fix it.
  if (true) {
    return a + b;
  }
  return 0;
}

console.log(calculate(5, 3));

The style/noVar and complexity/noExtraBooleanCast issues are automatically fixed. The noUnusedVariables rule might require manual intervention or specific configuration for auto-removal.

Integrating Biome into Your Workflow

For Biome to be truly effective, it needs to be seamlessly integrated into your daily development workflow.

1. VS Code Extension

The most convenient way to use Biome is through its official VS Code extension. Install it from the VS Code Marketplace. Once installed and configured, it will automatically format and lint your code on save, providing instant feedback directly in your editor.

To enable auto-formatting on save in VS Code, ensure you have the Biome extension installed and add/verify these settings in your settings.json:

{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[json]": {
    "editor.defaultFormatter": "biomejs.biome"
  }
}

2. Pre-commit Hooks (Husky & lint-staged)

To ensure that only properly formatted and linted code makes it into your version control, integrate Biome with pre-commit hooks. Tools like husky and lint-staged are perfect for this.

  1. Install Husky and lint-staged:

    npm install --save-dev husky lint-staged
  2. Initialize Husky:

    npx husky install
  3. Add a pre-commit hook:

    npx husky add .husky/pre-commit "npx lint-staged"
  4. Configure lint-staged in package.json:

    {
      "name": "my-biome-project",
      "version": "1.0.0",
      "devDependencies": {
        "@biomejs/biome": "latest",
        "husky": "latest",
        "lint-staged": "latest"
      },
      "scripts": {
        "prepare": "husky install",
        "format": "biome format --write .",
        "lint": "biome lint .",
        "lint:fix": "biome lint --apply .",
        "check": "biome check --apply ." 
      },
      "lint-staged": {
        "*.{js,jsx,ts,tsx,json}": [
          "biome check --write --no-errors-on-unmatched",
          "git add"
        ]
      }
    }

Now, whenever you commit, lint-staged will run biome check --write on only the staged files, ensuring they conform to your project's standards before being committed. The --no-errors-on-unmatched flag is useful if lint-staged passes files that Biome might ignore (e.g., non-JS/TS files).

3. CI/CD Integration

For continuous integration, you should add a Biome check step to your CI pipeline. This ensures that even if local checks are bypassed, the CI server will catch any non-compliant code.

Example for a GitHub Actions workflow (.github/workflows/ci.yml):

name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install dependencies
        run: npm ci
      - name: Run Biome checks
        # This will fail the CI if any formatting or linting errors are found
        run: npx @biomejs/biome check --config-path ./biome.json

This biome check command will run both formatting and linting. If any issues are found that cannot be automatically fixed, it will exit with a non-zero status, failing the build and preventing bad code from being merged.

Configuration Deep Dive: biome.json

The biome.json file is the heart of your Biome setup. It's a single, unified configuration file that governs all of Biome's operations. Let's break down its key sections.

{
  "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", 
  "extends": ["./other-config.json"],
  "files": {
    "ignore": [
      "node_modules",
      "dist",
      "**/legacy/**/*.js" 
    ],
    "include": [
      "src/**/*.js",
      "app/**/*.ts"
    ]
  },
  "formatter": {
    "enabled": true,
    "formatWithErrors": false, 
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100,
    "quoteStyle": "double",
    "semicolons": "always"
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "style": {
        "noImplicitBooleanConversion": "off", 
        "useConst": "error"
      },
      "correctness": {
        "noUnusedVariables": "error"
      }
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single", 
      "jsxQuoteStyle": "double"
    },
    "parser": {
      "unsafeParameterDecorators": false 
    }
  },
  "json": {
    "formatter": {
      "indentStyle": "space",
      "indentWidth": 2
    }
  }
}
  • $schema: (Optional) Provides JSON Schema validation for auto-completion and error checking in your editor.
  • extends: An array of paths to other biome.json files to extend. Useful for sharing configurations across multiple packages in a monorepo.
  • files: Defines which files Biome should process or ignore.
    • ignore: An array of glob patterns for files/directories to explicitly exclude. Always include node_modules and your build output directory (e.g., dist, build).
    • include: An array of glob patterns for files/directories to explicitly include. If not specified, Biome typically processes all supported files not in ignore.
  • formatter: Configures the code formatter.
    • enabled: true to enable the formatter.
    • formatWithErrors: If true, Biome will attempt to format files even if they have parsing errors.
    • Other options like indentStyle, indentWidth, lineWidth, quoteStyle, semicolons are self-explanatory.
  • linter: Configures the code linter.
    • enabled: true to enable the linter.
    • rules: Contains a nested object where you can enable/disable entire categories of rules (recommended: true), or configure individual rules with "off", "warn", or "error".
  • javascript, typescript, json (and others like html, css as they are implemented): These sections allow you to specify language-specific configurations that override the top-level settings. For example, you might want quoteStyle: "single" for JavaScript but jsxQuoteStyle: "double" for JSX.
    • parser: Contains options specific to the language's parser (e.g., unsafeParameterDecorators for TypeScript).

Advanced Features and Customization

Biome offers several features for fine-grained control over its behavior.

Ignoring Files/Directories

Beyond the files.ignore in biome.json, you can also use an .biomeignore file, similar to .gitignore. This is useful for project-specific ignores that you don't want to embed in biome.json.

Example .biomeignore:

# Ignore all files in the 'temp' directory
temp/

# Ignore a specific file
src/old-script.js

# Ignore all markdown files
*.md

Overriding Rules for Specific Files

Sometimes you need to apply different rules to a subset of files (e.g., test files, configuration files). Biome allows this using the overrides field in biome.json.

{
  "linter": {
    "rules": {
      "recommended": true
    }
  },
  "overrides": [
    {
      "include": ["src/test/**/*.js"],
      "linter": {
        "rules": {
          "style": {
            "noBannedTerms": "off" 
          }
        }
      }
    },
    {
      "include": ["*.config.js"],
      "linter": {
        "rules": {
          "style": {
            "useDefaultParameter": "off" 
          }
        }
      }
    }
  ]
}

In this example, the noBannedTerms rule is turned off specifically for test files, and useDefaultParameter is turned off for configuration files.

Migrating from ESLint/Prettier

Biome provides a convenient command to help migrate existing projects from ESLint and Prettier configurations.

npx @biomejs/biome migrate

This command attempts to read your existing .eslintrc.* and .prettierrc.* files and generate a biome.json that closely matches your current setup. While it might not perfectly translate every single rule, it's an excellent starting point and significantly reduces manual migration effort.

Performance Benchmarks and Real-World Impact

The most compelling argument for Biome, beyond its unified nature, is its blazing-fast performance. Being written in Rust, Biome leverages native code execution, efficient memory management, and parallel processing capabilities that JavaScript-based tools cannot match.

While exact benchmarks vary by project size and hardware, users consistently report significant speed improvements:

  • Local Development: Faster feedback loops when formatting and linting on save, leading to a smoother developer experience.
  • CI/CD Pipelines: Drastically reduced build and test times in continuous integration environments. A task that might take minutes with ESLint and Prettier could be completed in seconds with Biome, saving valuable time and resources.

This performance boost translates directly into increased developer productivity and cost savings for organizations, especially those with large codebases and frequent deployments.

Biome's Roadmap and Future

Biome is not just about formatting and linting; it's a comprehensive vision for the future of web development toolchains. While the formatter and linter are mature, the team is actively working on expanding its capabilities:

  • Bundler: A high-performance bundler to replace tools like Webpack or Rollup.
  • Compiler: A fast compiler for JavaScript and TypeScript, potentially replacing Babel or SWC.
  • Test Runner: An integrated test runner to handle unit and integration tests, similar to Jest or Vitest.
  • Language Server Protocol (LSP): Further enhancements to its LSP implementation for even better editor integration.

As Biome matures, it promises to offer an even more streamlined and performant development experience, potentially becoming the single command-line interface you need for most of your web project tasks.

Best Practices and Common Pitfalls

Best Practices

  1. Adopt Early: For new projects, start with Biome from day one. This avoids migration headaches later.
  2. Integrate into CI/CD: Always include biome check in your CI pipeline to enforce standards across the team.
  3. Use VS Code Extension: Leverage the Biome VS Code extension for real-time feedback and auto-formatting on save.
  4. Use Pre-commit Hooks: Implement lint-staged with biome check --write to ensure only compliant code is committed.
  5. Review Default Rules: While recommended: true is a great starting point, review the default linting rules and adjust them to your team's specific needs and preferences. Document any deviations.
  6. Keep biome.json Versioned: Store your biome.json in version control to ensure consistent behavior across all developer environments.
  7. Explore biome migrate: For existing projects, use npx @biomejs/biome migrate as a powerful tool to kickstart your transition.

Common Pitfalls

  1. Ignoring Performance Benefits: Not leveraging Biome's speed by only running it manually. Integrate it into automated workflows.
  2. Over-relying on --apply: While convenient, biome lint --apply-unsafe can sometimes change runtime behavior. Always review changes made by --apply-unsafe before committing.
  3. Incomplete Migration: Forgetting to remove old linters (ESLint) and formatters (Prettier) after migrating, leading to conflicts or redundant checks.
  4. Not Customizing Rules: Sticking strictly to defaults when your project has specific needs. Biome is configurable; tailor it to your context.
  5. Forgetting node_modules in ignore: If you encounter unexpected errors or slow performance, double-check that your node_modules and build directories are correctly ignored in biome.json or .biomeignore.

Conclusion

Biome represents a significant leap forward in web development toolchains. By consolidating essential development tasks into a single, high-performance, Rust-powered binary, it addresses many of the frustrations associated with fragmented JavaScript ecosystems. Its robust formatter and linter are already providing immense value, streamlining workflows, enforcing consistency, and drastically improving performance for developers worldwide.

As Biome continues to evolve and integrate bundling, compiling, and testing, it has the potential to become the de facto standard for modern web project tooling. Embracing Biome today means investing in a faster, more efficient, and more enjoyable development experience. Give it a try in your next project, and prepare to be impressed by its speed and simplicity.

CodewithYoha

Written by

CodewithYoha

Full-Stack Software Engineer with 5+ years of experience in Java, Spring Boot, and cloud architecture across AWS, Azure, and GCP. Writing production-grade engineering patterns for developers who ship real software.