Skip to content

What is Alchemy?

Alchemy is an Infrastructure-as-Effects framework. It extends Infrastructure-as-Code by combining your cloud resources and the application logic that uses them into a single, type-safe program powered by Effect.

Infrastructure-as-Code vs Infrastructure-as-Effects

Section titled “Infrastructure-as-Code vs Infrastructure-as-Effects”

Traditional IaC tools like Terraform, Pulumi, and CDK separate infrastructure definitions from application code. You write your infrastructure in one place and your business logic in another, then wire them together with environment variables, ARNs, and config files.

Alchemy takes a different approach: infrastructure and logic are Effects in the same program.

// alchemy.run.ts — infrastructure and logic in one program
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import Worker from "./src/worker.ts";
const Bucket = Cloudflare.R2.Bucket("Bucket");
export default Alchemy.Stack(
"MyApp",
{ providers: Cloudflare.providers(), state: Cloudflare.state() },
Effect.gen(function* () {
const bucket = yield* Bucket;
const worker = yield* Worker;
return { url: worker.url };
}),
);
// src/worker.ts — the Worker binds the Bucket directly
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
import { Bucket } from "./bucket.ts";
export default Cloudflare.Worker(
"Worker",
{ main: import.meta.url },
Effect.gen(function* () {
const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
return {
fetch: Effect.gen(function* () {
const obj = yield* bucket.get("hello.txt");
return obj
? HttpServerResponse.text(yield* obj.text())
: HttpServerResponse.text("Not found", { status: 404 });
}),
};
}),
);

There’s no separate “infra” project — it’s one program.

Alchemy uses TypeScript and Effect’s type system to catch mistakes at compile time. If you forget to provide the right providers for your resources, the compiler tells you:

import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
export default Alchemy.Stack(
"MyApp",
{
providers: Layer.empty,
Error ts(2322) ― Type 'Layer<never, never, never>' is not assignable to type 'Layer<NoInfer<Providers>, never, StackServices>'. Type 'Providers' is not assignable to type 'never'.
},
Effect.gen(function* () {
const bucket = yield* Cloudflare.R2.Bucket("Bucket");
}),
);

This error goes away when you provide Cloudflare.providers(). The type system ensures every resource has its provider wired up before you can deploy.

A Resource is any cloud entity managed by Alchemy — buckets, databases, queues, workers, IAM roles, DNS records, and more. Each resource is declared as an Effect and yield*-ed inside a Stack:

const bucket = yield* Cloudflare.R2.Bucket("Bucket");
const db = yield* Cloudflare.D1.Database("DB");
const queue = yield* AWS.SQS.Queue("Jobs");

Resources are just descriptions until they’re yielded. You can declare them in separate files and import them from anywhere:

src/bucket.ts
export const Bucket = Cloudflare.R2.Bucket("Bucket");
src/worker.ts
import { Bucket } from "./bucket.ts";
const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);

A Binding connects a resource to a Worker or Lambda. A single binding call hands your handler a typed client and — at deploy time — wires up the permissions, environment variables, and platform bindings that client needs:

const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
// later, inside fetch:
yield* bucket.put("hello.txt", "world");

bucket is the resource presented as a typed SDK. There’s no env.BUCKET, no BUCKET_NAME lookup, and no hand-written IAM policy — the binding is the client.

On AWS, the binding emits least-privilege IAM scoped to the exact resource ARN. On Cloudflare, it attaches the native Worker binding (R2, KV, D1, Durable Object). The runtime API is identical, so code written against one consumes the other.

Bindings also enable circular references between resources — Worker A can hold a typed client to Worker B and vice versa — which plain props, a directed acyclic graph, can’t express.

See Bindings for event sources, sinks, and the deploy-time mechanics.

A Stack is a collection of resources deployed together. Every deploy targets a stage — an isolated environment like dev, prod, or pr-42:

Terminal window
alchemy deploy # deploys to dev_$USER by default
alchemy deploy --stage prod # deploys to prod

Each stage has its own resources with distinct physical names, so environments never interfere with each other.

A Provider teaches Alchemy how to manage a resource type. Behind every resource is a provider implementing four lifecycle operations:

  • read — look up the resource’s live state in the cloud.
  • diff — decide whether a change is a no-op, an update, or a replacement.
  • reconcile — make the cloud match the desired state.
  • delete — remove the resource; idempotent.

The engine drives these in a plan → apply loop: read and diff build the plan, then reconcile and delete apply it in dependency order. See Providers and Resource lifecycle.

Providers are Effect Layers, wired into a Stack with Cloudflare.providers() or AWS.providers():

Alchemy.Stack(
"App",
{ providers: Cloudflare.providers(), state: Cloudflare.state() },
);

The type system checks the wiring — using a Cloudflare resource without Cloudflare.providers(), or providing the wrong cloud’s providers, is a compile-time error. To support a new cloud or third-party API, see Writing a Custom Resource Provider.

Alchemy supports two styles for writing Workers:

Effect style — your runtime code is an Effect, with typed errors, composable retries, and bindings resolved through the Effect system:

export default Cloudflare.Worker(
"Worker",
{ main: import.meta.url },
Effect.gen(function* () {
const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
return {
fetch: Effect.gen(function* () {
// Effect-native runtime code
}),
};
}),
);

Async style — your runtime code is a standard async fetch handler. Bindings are passed as props and you get a typed env object via InferEnv:

alchemy.run.ts
export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
export const Worker = Cloudflare.Worker("Worker", {
main: "./src/worker.ts",
env: { Bucket },
});
src/worker.ts
import type { WorkerEnv } from "../alchemy.run.ts";
export default {
async fetch(request: Request, env: WorkerEnv) {
const object = await env.Bucket.get("key");
return new Response(object?.body ?? null);
},
};

Both styles use the same infrastructure declarations, the same CLI, and the same deployment pipeline. Choose whichever fits your team.