Local Development
Alchemy supports local development mode where resources can be simulated locally using tools like Miniflare for Cloudflare Workers. This allows you to iterate quickly without deploying to production.
Enabling Local Mode
Run your Alchemy script with the --local or --dev flag:
bun ./alchemy.run.ts --local
Or enable it programmatically:
const app = await alchemy ( "my-app" , {
local: true
});
Local development mode is in beta. Report any issues to GitHub Issues .
How Local Mode Works
When local: true is set, Alchemy:
Skips cloud provider API calls for supported resources
Starts local simulators (like Miniflare for Cloudflare Workers)
Returns mock data for resources that donβt support local development
Binds local resources together so they can interact
Supported Resources
Cloudflare Workers
Workers run locally using Miniflare :
import alchemy from "alchemy" ;
import { Worker } from "alchemy/cloudflare" ;
import path from "node:path" ;
const app = await alchemy ( "my-app" , {
local: true
});
const worker = await Worker ( "api" , {
entrypoint: path . join ( import . meta . dirname , "src" , "index.ts" ),
bindings: {
API_KEY: alchemy . secret . env . API_KEY
}
});
console . log ( worker . url ); // http://localhost:8787
await app . finalize ();
Cloudflare D1 Database
D1 databases use local SQLite:
import { Worker , D1Database } from "alchemy/cloudflare" ;
const app = await alchemy ( "my-app" , {
local: true
});
const db = await D1Database ( "database" , {
name: "my-database"
});
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
bindings: {
DB: db // Binds to local SQLite database
}
});
await app . finalize ();
Cloudflare KV, R2, and More
Most Cloudflare resources work locally through Miniflare:
import { Worker , KVNamespace , R2Bucket } from "alchemy/cloudflare" ;
const app = await alchemy ( "my-app" , {
local: true
});
const kv = await KVNamespace ( "cache" , {
name: "my-cache"
});
const bucket = await R2Bucket ( "storage" , {
name: "my-bucket"
});
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
bindings: {
CACHE: kv ,
BUCKET: bucket
}
});
await app . finalize ();
Watch Mode
Combine local mode with watch mode for automatic reloading:
bun --watch ./alchemy.run.ts --local
Or use the Alchemy CLI:
This automatically:
Restarts your script when files change
Reloads local workers with new code
Preserves local state between reloads
Watch mode is perfect for rapid iteration during development.
Testing Local Resources
Once your resources are running locally, you can test them:
HTTP Requests
import alchemy from "alchemy" ;
import { Worker } from "alchemy/cloudflare" ;
const app = await alchemy ( "my-app" , {
local: true
});
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts"
});
console . log ( `Worker running at: ${ worker . url } ` );
await app . finalize ();
// Test with curl:
// curl http://localhost:8787
Programmatic Testing
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts"
});
await app . finalize ();
// Make request to local worker
const response = await fetch ( worker . url );
const data = await response . text ();
console . log ( data );
Local vs Production Differences
Resource Behavior
Some resources behave differently in local mode:
Local Mode
Production Mode
const app = await alchemy ( "my-app" , {
local: true
});
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts"
});
// worker.url = http://localhost:8787
// Runs in Miniflare locally
State Storage
In local mode, state is still persisted to .alchemy/ but resources run locally:
.alchemy/
my-app/
john/ # Your stage
state.json # Resource state
logs/ # Local process logs
pids/ # Local process PIDs
Conditional Local Behavior
Check if running in local mode within resources:
import { Scope } from "alchemy" ;
const scope = Scope . current ;
if ( scope . local ) {
console . log ( "Running in local development mode" );
// Use local configuration
} else {
console . log ( "Running in production mode" );
// Use production configuration
}
Environment Variables
Use different environment variables for local vs production:
.env.local
.env.production
ALCHEMY_PASSWORD = local-dev-password
API_KEY = test-api-key
DATABASE_URL = http://localhost:5432
import "dotenv/config" ;
import alchemy from "alchemy" ;
// Load appropriate .env file based on mode
const envFile = process . argv . includes ( "--local" )
? ".env.local"
: ".env.production" ;
const app = await alchemy ( "my-app" );
// Resources will use appropriate environment variables
Tunneling
Create a public URL for your local resources using tunnels:
bun ./alchemy.run.ts --local --tunnel
This creates a public URL you can share:
Worker running at: https://random-id.trycloudflare.com
Use tunnels to test webhooks and integrations that require public URLs.
Example: Full Local Development Setup
alchemy.run.ts
src/index.ts
import "dotenv/config" ;
import alchemy from "alchemy" ;
import { Worker , D1Database , R2Bucket } from "alchemy/cloudflare" ;
import path from "node:path" ;
const app = await alchemy ( "my-app" );
// Run with: bun --watch ./alchemy.run.ts --local
// Database (local SQLite in dev)
const db = await D1Database ( "database" , {
name: "my-database"
});
// Storage (local filesystem in dev)
const bucket = await R2Bucket ( "storage" , {
name: "my-bucket"
});
// Worker (local Miniflare in dev)
const worker = await Worker ( "api" , {
entrypoint: path . join ( import . meta . dirname , "src" , "index.ts" ),
bindings: {
DB: db ,
BUCKET: bucket ,
API_KEY: alchemy . secret . env . API_KEY
}
});
console . log ( `
π Worker running at: ${ worker . url }
Test with:
curl ${ worker . url }
curl ${ worker . url } /api/users
` );
await app . finalize ();
Troubleshooting
Port Already in Use
If port 8787 is already in use, specify a different port:
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
dev: {
port: 8788
}
});
Local State Not Persisting
Local state is stored in .alchemy/ - make sure this directory is not gitignored (only .alchemy/*.sqlite should be ignored):
# .gitignore
.alchemy/*.sqlite
.alchemy/logs/
.alchemy/pids/
Resources Not Reloading
Ensure youβre using --watch or bun --watch:
# β
This will reload on changes
bun --watch ./alchemy.run.ts --local
# β This won't reload
bun ./alchemy.run.ts --local
Best Practices
Use Local Mode for Development
Develop and test locally before deploying:
# Development
bun --watch ./alchemy.run.ts --local
# Production deploy
bun ./alchemy.run.ts
Separate Environment Variables
Keep local and production configs separate:
.env.local # Local development
.env.production # Production
.env.example # Template (commit this)
Test all functionality locally before deploying:
// Test script
import alchemy from "alchemy" ;
const app = await alchemy ( "my-app" , { local: true });
// Create resources...
await app . finalize ();
// Run tests
const response = await fetch ( worker . url + "/api/users" );
assert ( response . ok );
Use Tunnels for External Services
When testing webhooks or external integrations:
bun ./alchemy.run.ts --local --tunnel
# Use the public URL for webhook configuration
Next Steps