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
Initialize a monorepo workspace:
{
"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:"
}
}
{
"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"
}
}
Create turbo.json for orchestrating deployments:
{
"$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
}
}
}
The dependsOn configuration ensures resources are destroyed in the correct order, with frontend destroyed last.
Create Application Packages
Each app has its own alchemy.run.ts and can export resources:
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 ();
Each app package.json should export the alchemy.run.ts:
{
"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
This destroys resources in reverse dependency order:
Frontend (destroys last)
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:
# 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:
# Backend-specific configuration
BACKEND_PORT = 3000
BACKEND_LOG_LEVEL = debug
Loading Environment Variables
apps/backend/alchemy.run.ts
With local overrides
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:
apps/backend/package.json
Root package.json
{
"scripts" : {
"dev" : "bun --watch ./alchemy.run.ts --local" ,
"deploy" : "bun ./alchemy.run.ts" ,
"destroy" : "bun ./alchemy.run.ts --destroy"
}
}
Best Practices
apps/
api/ # Backend API
web/ # Frontend web app
admin/ # Admin dashboard
workers/ # Background workers
Only export resources that other apps need:
// Export public API
export const backend = await Worker ( "api" , { /* ... */ });
// Don't export internal resources
const internalDb = await D1Database ( "internal" , { /* ... */ });
Document which apps depend on which:
/**
* Frontend application
*
* Dependencies:
* - backend (API worker)
* - analytics (Analytics worker)
*/
import { backend } from "backend/alchemy" ;
import { analytics } from "analytics/alchemy" ;
Use Turborepo for Orchestration
Configure dependency graph in turbo.json:
{
"tasks" : {
"deploy" : {
"dependsOn" : [ "^deploy" ] // Deploy dependencies first
}
}
}
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:
package.json
apps/backend/alchemy.run.ts
apps/analytics/alchemy.run.ts
apps/frontend/alchemy.run.ts
{
"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:
{
"tasks" : {
"frontend#deploy" : {
"dependsOn" : [ "backend#deploy" , "analytics#deploy" ]
}
}
}
Next Steps