alchemy
Alchemy is a TypeScript-native Infrastructure-as-Code (IaC) library that lets you provision and manage cloud resources using pure TypeScript. Unlike Terraform or Pulumi, Alchemy resources are simple m
Alchemy
Alchemy is a TypeScript-native Infrastructure-as-Code (IaC) library that lets you provision and manage cloud resources using pure TypeScript. Unlike Terraform or Pulumi, Alchemy resources are simple memoized async functions that run in any JavaScript runtime, making infrastructure code embeddable directly in your application.
Quick References
| File | Purpose |
|---|---|
alchemy/src/index.ts | Main entry point and core exports |
alchemy/src/cloudflare/index.ts | Cloudflare provider resources |
alchemy/src/aws/index.ts | AWS provider resources |
README.md | Project overview and examples |
Providers
| Provider | Import Path | Description |
|---|---|---|
| Cloudflare | alchemy/cloudflare | Workers, R2, KV, D1, Queues, Durable Objects |
| AWS | alchemy/aws | Lambda, S3, DynamoDB, IAM, SQS, SES |
| Neon | alchemy/neon | Serverless Postgres (projects, branches) |
| PlanetScale | alchemy/planetscale | MySQL database (database, branches, passwords) |
| GitHub | alchemy/github | Repository webhooks, secrets, environments |
| Docker | alchemy/docker | Containers, images, networks, volumes |
| Stripe | alchemy/stripe | Products, prices, webhooks, customers |
| Upstash | alchemy/upstash | Serverless Redis |
| Vercel | alchemy/vercel | Projects and domains |
| ClickHouse | alchemy/clickhouse | Analytics database services |
When to Use
- Deploying Cloudflare Workers with bindings to KV, R2, D1, Durable Objects, and Queues
- Managing multi-cloud infrastructure with a single TypeScript codebase
- Embedding infrastructure provisioning directly in your application code
- Creating local development environments that mirror production
- Building serverless applications with automatic resource lifecycle management
Installation
npm install alchemy
For specific providers, install the required peer dependencies:
# Cloudflare
npm install alchemy wrangler @cloudflare/workers-types
# AWS
npm install alchemy @aws-sdk/client-lambda @aws-sdk/client-s3 @aws-sdk/client-dynamodb
Best Practices
-
Always call
app.finalize()at the end of your alchemy script to trigger cleanup of orphaned resources and ensure proper state management. -
Use
alchemy.secret()for sensitive values - Secrets are automatically encrypted in state files when a password is provided via theALCHEMY_PASSWORDenvironment variable. -
Leverage stages for environment isolation - The
stageoption (defaulting to your username) scopes all resources, allowing multiple developers or environments to coexist safely. -
Use explicit resource names with
adopt: true- When migrating existing infrastructure, use theadoptoption to take ownership of pre-existing resources. -
Configure a persistent state store for CI/CD - Use
CloudflareStateStoreorS3StateStorein production to avoid orphaned resources when running in CI environments.
Common Patterns
Initialize an Alchemy application:
import alchemy from "alchemy";
const app = await alchemy("my-app", {
stage: process.env.STAGE || "dev",
password: process.env.ALCHEMY_PASSWORD,
});
// ... create resources ...
await app.finalize();
Create a Cloudflare Worker with bindings:
import alchemy from "alchemy";
import { Worker, R2Bucket, KVNamespace, D1Database, Queue } from "alchemy/cloudflare";
const app = await alchemy("my-app");
const bucket = await R2Bucket("storage", { name: "my-storage" });
const kv = await KVNamespace("cache", { title: "my-cache" });
const db = await D1Database("db", { name: "my-database" });
const queue = await Queue("tasks", { name: "task-queue" });
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
bindings: {
BUCKET: bucket,
CACHE: kv,
DB: db,
TASKS: queue,
},
url: true,
});
console.log(`Worker URL: ${worker.url}`);
await app.finalize();
Use Durable Objects for state management:
import { Worker, DurableObjectNamespace } from "alchemy/cloudflare";
const counter = await DurableObjectNamespace("counter", {
className: "Counter",
sqlite: true,
});
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
bindings: {
COUNTER: counter,
},
});
Create encrypted secrets:
import alchemy from "alchemy";
const app = await alchemy("my-app", {
password: process.env.ALCHEMY_PASSWORD,
});
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
bindings: {
API_KEY: alchemy.secret(process.env.API_KEY),
// Or use the shorthand for environment variables:
DB_PASSWORD: alchemy.secret.env.DB_PASSWORD,
},
});
Deploy AWS Lambda with DynamoDB:
import alchemy from "alchemy";
import { Function, Table, Role, PolicyAttachment } from "alchemy/aws";
const app = await alchemy("aws-app");
const table = await Table("users", {
tableName: "users-table",
partitionKey: { name: "id", type: "S" },
});
const role = await Role("lambda-role", {
assumeRolePolicy: {
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
Action: "sts:AssumeRole",
}],
},
});
const fn = await Function("handler", {
functionName: "my-function",
handler: "index.handler",
runtime: "nodejs20.x",
bundle: { entryPoint: "./src/handler.ts" },
role: role,
environment: {
TABLE_NAME: table.tableName,
},
});
await app.finalize();
Local development with --dev flag:
// Run with: bun ./alchemy.run.ts --dev
const app = await alchemy("my-app");
// Resources automatically use local emulation when --dev is passed
const db = await D1Database("db", { name: "my-db" });
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
bindings: { DB: db },
url: true,
});
// Worker is available at localhost with hot reloading
console.log(worker.url); // http://localhost:8787
Nested scopes for grouped resources:
import alchemy from "alchemy";
import { Worker, KVNamespace } from "alchemy/cloudflare";
const app = await alchemy("my-app");
await alchemy.run("api-scope", async () => {
const cache = await KVNamespace("cache");
const worker = await Worker("api", {
entrypoint: "./src/api.ts",
bindings: { CACHE: cache },
});
});
await app.finalize();
API Quick Reference
| Export | Type | Description |
|---|---|---|
alchemy | function | Initialize an Alchemy application scope |
alchemy.secret() | function | Create an encrypted secret value |
alchemy.secret.env | proxy | Shorthand for alchemy.secret(process.env.X) |
alchemy.run() | function | Create a nested resource scope |
alchemy.destroy() | function | Programmatically destroy resources |
Resource | function | Define custom resource types |
Scope | class | Manage resource lifecycle and state |
Secret | class | Wrapper for sensitive values |
CLI Arguments
Alchemy scripts automatically parse these CLI arguments:
| Argument | Description |
|---|---|
--destroy | Delete all resources instead of creating/updating |
--dev / --local | Run resources locally with emulation |
--watch | Enable hot reloading for supported resources |
--stage <name> | Set the deployment stage |
--quiet | Suppress output logging |
--adopt | Adopt existing resources that match by name |
--force | Apply updates even without detected changes |
Environment Variables
| Variable | Description |
|---|---|
ALCHEMY_PASSWORD | Passphrase for encrypting/decrypting secrets |
ALCHEMY_STAGE | Default stage name (falls back to $USER) |
ALCHEMY_TELEMETRY_DISABLED | Set to disable anonymous telemetry |
DO_NOT_TRACK | Alternative way to disable telemetry |
State Management
By default, Alchemy stores state locally in .alchemy/{stage}/ directories. For CI/CD environments, configure a persistent state store:
import alchemy from "alchemy";
import { CloudflareStateStore } from "alchemy/cloudflare";
const app = await alchemy("my-app", {
stateStore: CloudflareStateStore({
accountId: process.env.CF_ACCOUNT_ID,
bucketName: "my-state-bucket",
}),
});
TypeScript Types
Access resource environment types for Workers:
import type { Worker } from "alchemy/cloudflare";
// Use the Worker's Env type in your worker code
type Env = typeof worker.Env;
export default {
async fetch(request: Request, env: Env) {
const value = await env.CACHE.get("key");
return new Response(value);
},
};
Framework Integrations
Alchemy provides Vite plugins for popular frameworks:
// vite.config.ts for React Router
import { reactRouter } from "@react-router/dev/vite";
import { cloudflare } from "alchemy/cloudflare/react-router";
export default defineConfig({
plugins: [
cloudflare(),
reactRouter(),
],
});
Available framework plugins:
alchemy/cloudflare/vite- Vite with Cloudflare Workersalchemy/cloudflare/react-router- React Router v7alchemy/cloudflare/tanstack-start- TanStack Startalchemy/cloudflare/sveltekit- SvelteKitalchemy/cloudflare/nuxt- Nuxt 3alchemy/cloudflare/astro- Astroalchemy/cloudflare/redwood- RedwoodJS