Skip to main content

Overview

The BE Monorepo is built using Bun workspaces, providing a modern, fast, and efficient way to manage multiple packages and applications in a single repository. This architecture enables code sharing, consistent tooling, and streamlined dependency management across the entire project.

Workspace Structure

The monorepo is organized into two main directories:
be-monorepo/
├── apps/              # Application packages
│   └── hono/         # Hono 4 backend app (port 3333)
├── packages/          # Shared packages
│   ├── core/         # Shared utilities and services
│   └── typescript-config/  # TypeScript configurations
└── package.json      # Root workspace configuration

Workspace Configuration

The root package.json defines the workspace structure:
package.json
{
  "name": "be-monorepo",
  "version": "1.0.0",
  "private": true,
  "packageManager": "[email protected]",
  "workspaces": [
    "apps/*",
    "packages/*"
  ]
}
The workspaces field tells Bun to treat all directories under apps/ and packages/ as individual packages within the monorepo.

Bun Workspace Features

Workspace Protocol

Packages use the workspace:* protocol to reference each other, ensuring they always use the local version:
apps/hono/package.json
{
  "dependencies": {
    "@workspace/core": "workspace:*"
  },
  "devDependencies": {
    "@workspace/typescript-config": "workspace:*"
  }
}

Isolated Linker

The project uses Bun’s isolated linker for better dependency isolation:
bunfig.toml
[install]
exact = true
linker = "isolated"
This configuration:
  • Installs exact versions (no semver ranges)
  • Provides isolated node_modules for better predictability
  • Prevents dependency conflicts between packages

Apps Directory

@workspace/hono

The Hono application is the main backend API server:
{
  "name": "@workspace/hono",
  "version": "1.0.0",
  "private": true,
  "type": "module"
}
Key Features:
  • Hono 4 web framework
  • PostgreSQL with Drizzle ORM
  • Better Auth authentication
  • OpenTelemetry observability
  • Port 3333 (development)
Dependencies:
  • @workspace/core - Shared utilities and services
  • @workspace/typescript-config - TypeScript configuration

Packages Directory

@workspace/core

Shared code used across applications:
packages/core/package.json
{
  "name": "@workspace/core",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    "./apis/*": "./src/apis/*.ts",
    "./assets/*": "./src/assets/*",
    "./constants/*": "./src/constants/*.ts",
    "./services/*": "./src/services/*.ts",
    "./types/*": "./src/types/*.ts",
    "./utils/*": "./src/utils/*.ts"
  }
}
Package Structure:
core/src/
├── apis/         # API client implementations
├── assets/       # Static assets
├── constants/    # Shared constants
├── services/     # Business logic services
├── types/        # TypeScript type definitions
└── utils/        # Utility functions
Usage Example:
// Import from @workspace/core in any app
import { logger } from "@workspace/core/utils/logger";
import { httpService } from "@workspace/core/services/http";
import { HTTP_STATUS } from "@workspace/core/constants/http";
The exports field provides granular control over which files can be imported, improving encapsulation and tree-shaking.

@workspace/typescript-config

Shared TypeScript configurations for consistent typing across the monorepo.
{
  "name": "@workspace/typescript-config",
  "version": "1.0.0",
  "private": true
}
Available Configurations:
  • base.json - Base configuration for all packages
  • node.json - Node.js-specific configuration
See TypeScript Configuration for details.

Workspace Commands

The root package.json provides scripts that work across the monorepo:

Filtering Commands

Run commands for specific packages using Bun’s --filter flag:
# Run dev script in the hono app
bun --filter @workspace/hono dev

# Shorthand defined in root package.json
bun hono dev

Parallel Execution

Run scripts across multiple packages in parallel:
# Run typecheck across all apps
bun run --parallel hono:typecheck

# Run multiple checks together
bun run --parallel lint hono:typecheck

Workspace-wide Scripts

package.json
{
  "scripts": {
    "clean": "rimraf --glob '**/node_modules' '**/dist' && bun install",
    "typecheck": "bun run --parallel hono:typecheck",
    "lint": "ultracite check",
    "format": "ultracite format"
  }
}

Benefits of This Architecture

1. Code Sharing

Share code seamlessly between applications without publishing to npm:
apps/hono/src/routes/users.ts
import { logger } from "@workspace/core/utils/logger";
import { httpService } from "@workspace/core/services/http";

export async function getUsers() {
  logger.info("Fetching users");
  return httpService.get("/users");
}

2. Consistent Tooling

All packages share the same:
  • TypeScript configuration
  • Linting rules (Biome)
  • Formatting standards
  • Build tools

3. Dependency Management

Single bun.lock file for the entire monorepo:
  • Faster installs
  • Consistent versions
  • Reduced disk space
  • Simplified security audits

4. Atomic Changes

Make changes across multiple packages in a single commit:
# Change a shared utility and all consumers in one PR
git add packages/core/src/utils/logger.ts
git add apps/hono/src/routes/users.ts
git commit -m "feat: add structured logging"

5. Type Safety Across Boundaries

TypeScript types flow seamlessly between packages:
packages/core/src/types/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
}
apps/hono/src/routes/users.ts
import type { User } from "@workspace/core/types/core";

// TypeScript knows about User interface
function processUser(user: User) {
  console.log(user.name); // ✅ Type-safe
}

Path Aliases

Workspace packages can be imported using TypeScript path aliases:
apps/hono/tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@workspace/core/*": ["../../packages/core/src/*"]
    }
  }
}
This enables direct source imports for better development experience:
// Development: imports from source
import { logger } from "@workspace/core/utils/logger";

// Production: imports from compiled output
// (handled by build process)

Best Practices

Each package should have a single, clear responsibility. The @workspace/core package contains utilities used across apps, while app-specific code stays in the app directory.
Always use workspace:* for internal dependencies to ensure you’re using the local version during development.
Use the exports field to control what other packages can import, preventing accidental dependencies on internal implementation details.
Extract common configurations (TypeScript, ESLint, etc.) into shared packages to ensure consistency.

Adding New Packages

To add a new package to the monorepo:
  1. Create the package directory:
    mkdir -p packages/my-package/src
    
  2. Add a package.json:
    {
      "name": "@workspace/my-package",
      "version": "1.0.0",
      "type": "module",
      "private": true
    }
    
  3. Install dependencies in the root:
    bun install
    
  4. Reference it in other packages:
    {
      "dependencies": {
        "@workspace/my-package": "workspace:*"
      }
    }
    
Always run bun install from the root directory to update the workspace dependency graph.

Next Steps

Environment Variables

Learn how to configure environment variables across the monorepo

TypeScript Config

Understand the shared TypeScript configuration

Build docs developers (and LLMs) love