Skip to main content

Monorepo Setup

Alchemy works seamlessly in monorepo setups, allowing you to manage multiple applications and shared infrastructure in a single repository. This guide shows you how to structure and deploy a monorepo with Alchemy.

Monorepo Structure

A typical Alchemy monorepo looks like this:
my-monorepo/
  apps/
    frontend/
      alchemy.run.ts
      src/
      package.json
    backend/
      alchemy.run.ts
      src/
      package.json
    analytics/
      alchemy.run.ts
      src/
      package.json
  package.json          # Root package.json
  turbo.json            # Turborepo config (optional)
  tsconfig.json         # Shared TypeScript config
  .env.example

Setting Up a Monorepo

1
Create Root Package
2
Initialize a monorepo workspace:
3
package.json (npm/bun)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": {
    "packages": [
      "apps/*"
    ],
    "catalog": {
      "alchemy": "latest",
      "typescript": "^5.8.3"
    }
  },
  "scripts": {
    "build": "tsc -b",
    "deploy": "turbo run deploy",
    "destroy": "turbo run destroy"
  },
  "devDependencies": {
    "alchemy": "catalog:",
    "turbo": "^2.5.6",
    "typescript": "catalog:"
  }
}
package.json (pnpm)
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "tsc -b",
    "deploy": "turbo run deploy",
    "destroy": "turbo run destroy"
  },
  "devDependencies": {
    "alchemy": "latest",
    "turbo": "^2.5.6",
    "typescript": "^5.8.3"
  }
}
4
Configure Turborepo
5
Create turbo.json for orchestrating deployments:
6
{
  "$schema": "https://turborepo.com/schema.json",
  "ui": "tui",
  "tasks": {
    "deploy": {
      "dependsOn": ["^deploy"],
      "cache": false
    },
    "dev": {
      "persistent": true,
      "cache": false
    },
    "analytics#destroy": {
      "cache": false,
      "dependsOn": ["frontend#destroy"]
    },
    "backend#destroy": {
      "cache": false,
      "dependsOn": ["frontend#destroy"]
    },
    "frontend#destroy": {
      "cache": false
    }
  }
}
7
The dependsOn configuration ensures resources are destroyed in the correct order, with frontend destroyed last.
8
Create Application Packages
9
Each app has its own alchemy.run.ts and can export resources:
10
apps/backend/alchemy.run.ts
import alchemy from "alchemy";
import { Worker, D1Database } from "alchemy/cloudflare";
import path from "node:path";

const app = await alchemy("backend");

const db = await D1Database("db");

// Export for use in other apps
export const backend = await Worker("worker", {
  entrypoint: path.join(import.meta.dirname, "src", "index.ts"),
  bindings: {
    DB: db,
    API_KEY: alchemy.secret.env.API_KEY
  }
});

if (import.meta.main) {
  console.log({ url: backend.url });
}

await app.finalize();
apps/analytics/alchemy.run.ts
import alchemy from "alchemy";
import { Worker } from "alchemy/cloudflare";
import path from "node:path";

const app = await alchemy("analytics");

// Export for use in other apps
export const analytics = await Worker("worker", {
  entrypoint: path.join(import.meta.dirname, "src", "index.ts"),
  bindings: {
    API_KEY: alchemy.secret.env.API_KEY
  }
});

if (import.meta.main) {
  console.log({ url: analytics.url });
}

await app.finalize();
apps/frontend/alchemy.run.ts
import alchemy from "alchemy";
import { Vite } from "alchemy/cloudflare";
// Import resources from other apps
import { analytics } from "analytics/alchemy";
import { backend } from "backend/alchemy";

const app = await alchemy("frontend");

export const frontend = await Vite("website", {
  bindings: {
    backend,      // Use backend worker
    analytics     // Use analytics worker
  }
});

console.log({
  url: frontend.url
});

await app.finalize();
11
Configure Package Exports
12
Each app package.json should export the alchemy.run.ts:
13
{
  "name": "backend",
  "type": "module",
  "exports": {
    "./alchemy": "./alchemy.run.ts"
  },
  "scripts": {
    "deploy": "bun ./alchemy.run.ts",
    "destroy": "bun ./alchemy.run.ts --destroy"
  },
  "dependencies": {
    "alchemy": "catalog:"
  }
}

Resource Sharing Between Apps

Apps can import and use resources from other apps:

Export Resources

apps/backend/alchemy.run.ts
import alchemy from "alchemy";
import { Worker, D1Database } from "alchemy/cloudflare";

const app = await alchemy("backend");

const db = await D1Database("db", {
  name: "shared-database"
});

// Export both the worker and database
export const backend = await Worker("api", {
  entrypoint: "./src/index.ts",
  bindings: { DB: db }
});

export const database = db;

await app.finalize();

Import and Use Resources

apps/frontend/alchemy.run.ts
import alchemy from "alchemy";
import { Vite } from "alchemy/cloudflare";
import { backend, database } from "backend/alchemy";

const app = await alchemy("frontend");

const frontend = await Vite("website", {
  bindings: {
    API: backend,        // Reference to backend worker
    DB: database         // Direct access to backend's database
  }
});

await app.finalize();
Alchemy automatically handles resource dependencies and creates them in the correct order.

Deployment

Deploy All Apps

Deploy all applications in dependency order:
# Using Turborepo
bun run deploy

# Or manually
cd apps/backend && bun ./alchemy.run.ts
cd apps/analytics && bun ./alchemy.run.ts
cd apps/frontend && bun ./alchemy.run.ts
Turborepo ensures:
  • Dependencies are deployed first
  • Apps are deployed in parallel when possible
  • Failures are handled gracefully

Deploy Specific App

cd apps/backend
bun ./alchemy.run.ts

Deploy with Stage

bun run deploy -- --stage production

Destroying Resources

Destroy All Apps

bun run destroy
This destroys resources in reverse dependency order:
  1. Frontend (destroys last)
  2. Backend and Analytics (can destroy in parallel)

Destroy Specific App

cd apps/backend
bun ./alchemy.run.ts --destroy
Be careful when destroying apps with shared resources. Other apps may depend on them.

Environment Variables

Shared Environment Variables

Use a root .env file for shared configuration:
.env
# Shared credentials
ALCHEMY_PASSWORD=shared-password
CLOUDFLARE_API_KEY=your-api-key
CLOUDFLARE_ACCOUNT_ID=your-account-id

# Shared secrets
API_KEY=shared-api-key
DATABASE_URL=postgres://...

App-Specific Environment Variables

Each app can have its own .env.local:
apps/backend/.env.local
# Backend-specific configuration
BACKEND_PORT=3000
BACKEND_LOG_LEVEL=debug

Loading Environment Variables

import "dotenv/config";  // Loads from root .env
import alchemy from "alchemy";

// Access shared environment variables
const app = await alchemy("backend", {
  password: process.env.ALCHEMY_PASSWORD
});

Stages and Environments

Per-Stage Deployments

Deploy different stages of the monorepo:
# Development
bun run deploy -- --stage dev

# Staging
bun run deploy -- --stage staging

# Production
bun run deploy -- --stage production

Environment-Specific Configuration

apps/backend/alchemy.run.ts
import alchemy from "alchemy";

const app = await alchemy("backend");
const stage = app.stage;  // "dev", "staging", or "production"

const worker = await Worker("api", {
  entrypoint: "./src/index.ts",
  bindings: {
    // Environment-specific configuration
    LOG_LEVEL: stage === "production" ? "error" : "debug",
    API_URL: stage === "production" 
      ? "https://api.example.com"
      : "https://staging-api.example.com"
  }
});

await app.finalize();

State Management

Each application maintains its own state:
.alchemy/
  backend/
    dev/
      state.json
    production/
      state.json
  frontend/
    dev/
      state.json
    production/
      state.json
  analytics/
    dev/
      state.json
    production/
      state.json
State is isolated per application and stage, allowing independent deployments.

Development Workflow

Local Development

Run all apps locally:
# Using Turborepo
bun run dev

# Or manually
cd apps/backend && bun --watch ./alchemy.run.ts --local &
cd apps/analytics && bun --watch ./alchemy.run.ts --local &
cd apps/frontend && bun --watch ./alchemy.run.ts --local

Watch Mode

Each app can run in watch mode independently:
{
  "scripts": {
    "dev": "bun --watch ./alchemy.run.ts --local",
    "deploy": "bun ./alchemy.run.ts",
    "destroy": "bun ./alchemy.run.ts --destroy"
  }
}

Best Practices

1
Use Consistent Naming
2
Name apps consistently:
3
apps/
  api/         # Backend API
  web/         # Frontend web app
  admin/       # Admin dashboard
  workers/     # Background workers
4
Export Shared Resources
5
Only export resources that other apps need:
6
// Export public API
export const backend = await Worker("api", { /* ... */ });

// Don't export internal resources
const internalDb = await D1Database("internal", { /* ... */ });
7
Document Dependencies
8
Document which apps depend on which:
9
/**
 * Frontend application
 * 
 * Dependencies:
 * - backend (API worker)
 * - analytics (Analytics worker)
 */
import { backend } from "backend/alchemy";
import { analytics } from "analytics/alchemy";
10
Use Turborepo for Orchestration
11
Configure dependency graph in turbo.json:
12
{
  "tasks": {
    "deploy": {
      "dependsOn": ["^deploy"]  // Deploy dependencies first
    }
  }
}
13
Separate Concerns
14
Keep apps focused:
15
apps/
  api/          # REST API
  graphql/      # GraphQL API
  web/          # User-facing web app
  admin/        # Admin dashboard
  cron/         # Scheduled tasks

Example: Complete Monorepo

Here’s a complete example monorepo:
{
  "name": "monorepo",
  "private": true,
  "scripts": {
    "build": "tsc -b",
    "dev": "turbo run dev",
    "deploy": "tsc -b && turbo run deploy --ui=stream",
    "destroy": "tsc -b && turbo run destroy --ui=stream"
  },
  "workspaces": {
    "packages": ["apps/*"],
    "catalog": {
      "alchemy": "latest",
      "typescript": "^5.8.3"
    }
  },
  "devDependencies": {
    "alchemy": "catalog:",
    "turbo": "^2.5.6",
    "typescript": "catalog:"
  }
}

Troubleshooting

Circular Dependencies

Avoid circular imports between apps:
// ❌ Bad: frontend imports backend, backend imports frontend
// apps/backend/alchemy.run.ts
import { frontend } from "frontend/alchemy";

// apps/frontend/alchemy.run.ts
import { backend } from "backend/alchemy";
Solution: Create a shared package for common resources.

Import Errors

Ensure package exports are configured:
apps/backend/package.json
{
  "exports": {
    "./alchemy": "./alchemy.run.ts"
  }
}

Deployment Order

Use Turborepo’s dependsOn to control order:
turbo.json
{
  "tasks": {
    "frontend#deploy": {
      "dependsOn": ["backend#deploy", "analytics#deploy"]
    }
  }
}

Next Steps

Build docs developers (and LLMs) love