| 1 | --- |
| 2 | title: What is Alchemy? |
| 3 | description: Alchemy is Infrastructure as Code built in pure Effect, with Infrastructure as Effects on top. Declare your cloud resources and the code that runs on them in one type-safe TypeScript program, and deploy it with one command. |
| 4 | prev: |
| 5 | link: /getting-started |
| 6 | label: Getting started |
| 7 | next: |
| 8 | link: /infrastructure-as-effects/runtime |
| 9 | label: Runtime |
| 10 | --- |
| 11 | |
| 12 | import DeployTerminal from "../../components/marketing-islands/DeployTerminal.tsx"; |
| 13 | import ProviderIcons from "../../components/ProviderIcons.astro"; |
| 14 | |
| 15 | Alchemy is **Infrastructure as Code** built in pure |
| 16 | [Effect](https://effect.website), with **Infrastructure as Effects** |
| 17 | on top. Infrastructure as Code declares, diffs, and deploys cloud |
| 18 | resources the way Terraform or Pulumi does. Infrastructure as Effects |
| 19 | lets the code that runs on those resources live in the same program, |
| 20 | as typed Effects and Layers. |
| 21 | |
| 22 | Here is the whole thing in one file. An R2 Bucket, and a Worker that |
| 23 | serves files from it: |
| 24 | |
| 25 | ```typescript |
| 26 | // src/api.ts |
| 27 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 28 | import * as Effect from "effect/Effect"; |
| 29 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 30 | |
| 31 | export const Uploads = Cloudflare.R2.Bucket("Uploads"); |
| 32 | |
| 33 | export default Cloudflare.Worker( |
| 34 | "Api", |
| 35 | { main: import.meta.url }, |
| 36 | Effect.gen(function* () { |
| 37 | const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads); |
| 38 | |
| 39 | return { |
| 40 | fetch: Effect.gen(function* () { |
| 41 | const obj = yield* bucket.get("hello.txt"); |
| 42 | return obj |
| 43 | ? HttpServerResponse.text(yield* obj.text()) |
| 44 | : HttpServerResponse.text("Not found", { status: 404 }); |
| 45 | }), |
| 46 | }; |
| 47 | }).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)), |
| 48 | ); |
| 49 | ``` |
| 50 | |
| 51 | That one file is a complete application. The Bucket is declared next |
| 52 | to the Worker that uses it, and `bucket` is a typed client the handler |
| 53 | closes over. Let's start with Infrastructure as Code, then look at what |
| 54 | Infrastructure as Effects adds on top. |
| 55 | |
| 56 | ## Infrastructure as Code |
| 57 | |
| 58 | The core of Alchemy is Infrastructure as Code, in the same family as |
| 59 | Terraform, Pulumi, CloudFormation, and the CDK. |
| 60 | |
| 61 | ### Stack |
| 62 | |
| 63 | A **Stack** is the unit you deploy, an Effect that yields resources |
| 64 | and returns the outputs you want printed: |
| 65 | |
| 66 | ```typescript |
| 67 | // alchemy.run.ts |
| 68 | import * as Alchemy from "alchemy"; |
| 69 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 70 | import * as Effect from "effect/Effect"; |
| 71 | import Api from "./src/api.ts"; |
| 72 | |
| 73 | export default Alchemy.Stack( |
| 74 | "MyApp", |
| 75 | { providers: Cloudflare.providers(), state: Cloudflare.state() }, |
| 76 | Effect.gen(function* () { |
| 77 | const api = yield* Api; |
| 78 | return { url: api.url }; |
| 79 | }), |
| 80 | ); |
| 81 | ``` |
| 82 | |
| 83 | Deploy it: |
| 84 | |
| 85 | ```sh |
| 86 | bun alchemy deploy |
| 87 | ``` |
| 88 | |
| 89 | Alchemy creates the Bucket, bundles the Worker, wires the binding |
| 90 | between them, and prints the URL. Every deploy targets a stage, an isolated environment with its own |
| 91 | copy of every resource, so `dev` and `prod` never touch each other: |
| 92 | |
| 93 | ```sh |
| 94 | alchemy deploy # dev_$USER by default |
| 95 | alchemy deploy --stage prod |
| 96 | ``` |
| 97 | |
| 98 | Alchemy reads the current state, diffs it against your program, shows |
| 99 | the plan, and applies it in dependency order. Here is that loop for a |
| 100 | Stack with a Bucket, a KV Namespace, and a Worker bound to both: |
| 101 | |
| 102 | <DeployTerminal client:visible /> |
| 103 | |
| 104 | **State** is where the result persists between runs, so the next |
| 105 | deploy only touches what changed — the `state` option above picks |
| 106 | where it's stored. |
| 107 | |
| 108 | ### Resource |
| 109 | |
| 110 | A **Resource** is a cloud entity in a Stack managed by Alchemy: a |
| 111 | bucket, a database, a queue, a Worker, a DNS record. Yield it in the |
| 112 | Stack to add it to the plan: |
| 113 | |
| 114 | ```typescript |
| 115 | const bucket = yield* Cloudflare.R2.Bucket("Uploads"); |
| 116 | // bucket.bucketName is an Output |
| 117 | ``` |
| 118 | |
| 119 | Each Resource has a logical id, `"Uploads"` here, that Alchemy uses to |
| 120 | track it across deploys. Its outputs are typed values you can pass |
| 121 | into other Resources. Here the physical bucket name Alchemy generated |
| 122 | becomes an environment variable on a Worker: |
| 123 | |
| 124 | ```typescript |
| 125 | const bucket = yield* Cloudflare.R2.Bucket("Uploads"); |
| 126 | |
| 127 | yield* Cloudflare.Worker("Api", { |
| 128 | main: "./src/api.ts", |
| 129 | env: { BUCKET_NAME: bucket.bucketName }, |
| 130 | }); |
| 131 | ``` |
| 132 | |
| 133 | ### Provider |
| 134 | |
| 135 | A **Provider** teaches Alchemy how to read, diff, create, update, and |
| 136 | delete one resource type. Each cloud ships its providers as an Effect |
| 137 | Layer, and a Stack takes as many as it needs: |
| 138 | |
| 139 | ```typescript |
| 140 | providers: Layer.mergeAll(Cloudflare.providers(), AWS.providers()), |
| 141 | ``` |
| 142 | |
| 143 | The type system checks the wiring. Yield an AWS resource in a Stack |
| 144 | that only provides `Cloudflare.providers()` and the program does not |
| 145 | compile. [Providers](/infrastructure-as-code/provider) covers the |
| 146 | built-in ones, and |
| 147 | [Custom Provider](/infrastructure-as-code/custom-provider) shows how |
| 148 | to write your own. |
| 149 | |
| 150 | ## A superset of Infrastructure as Code |
| 151 | |
| 152 | With only Infrastructure as Code, Alchemy looks like and functions like any other |
| 153 | IaC tool. One file |
| 154 | declares the Bucket and the Worker, passing the Bucket on the Worker's |
| 155 | `env`. Another file is the handler, reaching for the Bucket through |
| 156 | that env: |
| 157 | |
| 158 | ```typescript |
| 159 | // alchemy.run.ts |
| 160 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 161 | |
| 162 | export const Uploads = Cloudflare.R2.Bucket("Uploads"); |
| 163 | |
| 164 | export const Api = Cloudflare.Worker("Api", { |
| 165 | main: "./src/api.ts", |
| 166 | env: { Uploads }, |
| 167 | }); |
| 168 | |
| 169 | export type ApiEnv = Cloudflare.InferEnv<typeof Api>; |
| 170 | ``` |
| 171 | |
| 172 | ```typescript |
| 173 | // src/api.ts |
| 174 | import type { ApiEnv } from "../alchemy.run.ts"; |
| 175 | |
| 176 | export default { |
| 177 | async fetch(request: Request, env: ApiEnv) { |
| 178 | const obj = await env.Uploads.get("hello.txt"); |
| 179 | return obj |
| 180 | ? new Response(await obj.text()) |
| 181 | : new Response("Not found", { status: 404 }); |
| 182 | }, |
| 183 | }; |
| 184 | ``` |
| 185 | |
| 186 | This works, and it is fully supported. `InferEnv` even types the env |
| 187 | from the declaration, which is more than most IaC tools give you. But |
| 188 | the two files are still held together by the name `Uploads`, and the |
| 189 | handler knows nothing about how the Bucket got there. Infrastructure |
| 190 | as Effects is what turns those two files into the one at the top of |
| 191 | this page. |
| 192 | |
| 193 | :::tip |
| 194 | Alchemy also supports circular references in the |
| 195 | dependency graph, which most IaC tools reject. See |
| 196 | [Circular Bindings](/infrastructure-as-effects/circular-bindings). |
| 197 | ::: |
| 198 | |
| 199 | ## Infrastructure as Effects |
| 200 | |
| 201 | Infrastructure as Effects adds two new concepts to Infrastructure as |
| 202 | Code: Runtimes and Bindings. |
| 203 | |
| 204 | ### Runtime |
| 205 | |
| 206 | A **Runtime** is a Resource that carries the code it runs: a Worker, |
| 207 | Lambda Function, Container, or Server. That code is always written |
| 208 | the same way, as an **Effectful Constructor**. Bind what you need, |
| 209 | then return what you expose: |
| 210 | |
| 211 | ```typescript |
| 212 | Effect.gen(function* () { |
| 213 | // bind what you need |
| 214 | const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads); |
| 215 | |
| 216 | // return what you expose |
| 217 | return { fetch: /* ... */ }; |
| 218 | }); |
| 219 | ``` |
| 220 | |
| 221 | The outer Effect runs both at deploy time and at cold start; what it |
| 222 | returns runs per request. A Worker returns `fetch`, a Durable Object |
| 223 | returns its RPC methods, a Workflow returns its run function, and |
| 224 | every Runtime in Alchemy is a variation on that one shape. |
| 225 | |
| 226 | ### Binding |
| 227 | |
| 228 | A **Binding** connects a Resource to the Runtime that uses it: |
| 229 | |
| 230 | ```typescript |
| 231 | const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads); |
| 232 | // bucket.get / bucket.put / bucket.list, typed end to end |
| 233 | ``` |
| 234 | |
| 235 | It hands back a typed client and generates whatever that client needs |
| 236 | to work. On Cloudflare that is a native Worker binding. On AWS it is |
| 237 | an IAM statement scoped to one resource, plus the resource's name in |
| 238 | the Function's environment: |
| 239 | |
| 240 | ```typescript |
| 241 | const getItem = yield* AWS.DynamoDB.GetItem(Jobs); |
| 242 | // → { Action: ["dynamodb:GetItem"], Resource: [Jobs.tableArn] } |
| 243 | // → Jobs_tableName=Jobs-a1b2c3 |
| 244 | ``` |
| 245 | |
| 246 | There is no `env.Uploads` to reach for and no hand-written policy. The |
| 247 | binding is the SDK. |
| 248 | |
| 249 | ### Layers |
| 250 | |
| 251 | Every Binding is a contract paired with a **Layer** that fulfills it: |
| 252 | the handler is written against the contract, and the Layer decides |
| 253 | how — swap the Layer and the handler doesn't change: |
| 254 | |
| 255 | ```diff lang="typescript" |
| 256 | Effect.gen(function* () { |
| 257 | const bucket = yield* Cloudflare.R2.ReadWriteBucket(Uploads); |
| 258 | // ... |
| 259 | -}).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)); // native Worker binding |
| 260 | +}).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketHttp)); // HTTP with a scoped token |
| 261 | ``` |
| 262 | |
| 263 | The same split works for services of your own. A Layer can own |
| 264 | Resources and Bindings outright: |
| 265 | |
| 266 | ```typescript |
| 267 | export const JobServiceKV = Layer.effect( |
| 268 | JobService, |
| 269 | Effect.gen(function* () { |
| 270 | const Jobs = yield* Cloudflare.KV.Namespace("Jobs"); // a Resource, owned by the Layer |
| 271 | const kv = yield* Cloudflare.KV.ReadWriteNamespace(Jobs); // and its Binding |
| 272 | return { getJob: (id: string) => kv.get<Job>(id, "json") }; |
| 273 | }), |
| 274 | ); |
| 275 | ``` |
| 276 | |
| 277 | Business logic is implemented against the `JobService` contract. The |
| 278 | Layer decides the infrastructure behind it, so swapping KV for |
| 279 | DynamoDB is one line: |
| 280 | |
| 281 | ```diff lang="typescript" |
| 282 | Effect.gen(function* () { |
| 283 | const jobs = yield* JobService; |
| 284 | // ... |
| 285 | -}).pipe(Effect.provide(JobServiceKV)); // a KV Namespace, created and bound |
| 286 | +}).pipe(Effect.provide(JobServiceDynamo)); // now a DynamoDB Table instead |
| 287 | ``` |
| 288 | |
| 289 | The Infrastructure as Effects section goes deeper from here: |
| 290 | [Event Sources](/infrastructure-as-effects/event-sources) and |
| 291 | [Sinks](/infrastructure-as-effects/sinks) turn resources into Effect |
| 292 | `Stream`s, [Phases](/infrastructure-as-effects/phases) pins down |
| 293 | exactly what runs when, |
| 294 | [Circular Bindings](/infrastructure-as-effects/circular-bindings) |
| 295 | lets two Workers call each other, and |
| 296 | [Telemetry](/infrastructure-as-effects/telemetry) exports every |
| 297 | request's traces and logs. |
| 298 | |
| 299 | ## Where next |
| 300 | |
| 301 | <div class="next-steps"> |
| 302 | |
| 303 | <a href="/infrastructure-as-effects/runtime" class="next-card next-card--recommended"> |
| 304 | <strong>Runtime</strong> |
| 305 | <span>The Effectful Constructor in depth: the fetch and RPC interface, and the three ways to declare a Runtime.</span> |
| 306 | </a> |
| 307 | |
| 308 | <a href="/cloudflare" class="next-card"> |
| 309 | <strong>Cloudflare</strong> |
| 310 | <span>Workers, Durable Objects, R2, KV, D1, Queues, Workflows, and Containers, with a tutorial and a guide for each.</span> |
| 311 | </a> |
| 312 | |
| 313 | <a href="/aws" class="next-card"> |
| 314 | <strong>AWS</strong> |
| 315 | <span>Lambda, S3, SQS, DynamoDB, EC2, ECS, and EKS, with a tutorial and a guide for each.</span> |
| 316 | </a> |
| 317 | |
| 318 | <a href="/providers" class="next-card"> |
| 319 | <strong>More providers</strong> |
| 320 | <ProviderIcons /> |
| 321 | </a> |
| 322 | |
| 323 | </div> |