1---
2title: File layout
3description: How to organize an Alchemy project — one file per Resource or Layer, group Resources that travel together by concern, and keep alchemy.run.ts as the composition root.
4---
5
6A typical alchemy app ([`examples/cloudflare-neon-drizzle`](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-neon-drizzle)):
7
8```sh
9.
10├── alchemy.run.ts # composition root — export default the Stack
11├── src/
12│ ├── Api.ts # the Worker (Function/Server + its runtime code)
13│ ├── Db.ts # Database + Branch + Hyperdrive — one concern
14│ └── schema.ts # Drizzle schema shared by Db.ts and Api.ts
15└── test/
16 └── integ.test.ts # deploys the Stack, hits the live URL
17```
18
19Three rules produce this shape: one file per Resource or Layer,
20group Resources that travel together, and keep `alchemy.run.ts`
21as the composition root. This page walks a project from one file
22to that shape.
23
24## Start with one file
25
26Everything in `alchemy.run.ts` is a perfectly good starting
27point:
28
29```typescript
30// alchemy.run.ts
31import * as Alchemy from "alchemy";
32import * as Cloudflare from "alchemy/Cloudflare";
33import * as Effect from "effect/Effect";
34
35export const DB = Cloudflare.D1.Database("DB");
36
37export const Bucket = Cloudflare.R2.Bucket("Bucket");
38
39export const Worker = Cloudflare.Worker("Worker", {
40 main: "./src/worker.ts",
41 env: { DB, Bucket },
42});
43
44export default Alchemy.Stack(
45 "CloudflareWorker",
46 {
47 providers: Cloudflare.providers(),
48 state: Cloudflare.state(),
49 },
50 Effect.gen(function* () {
51 const worker = yield* Worker;
52 return { url: worker.url.as<string>() };
53 }),
54);
55```
56
57This is fine until the file accumulates runtime code, stage
58logic, and a dozen Resources — then split.
59
60## One file per Resource
61
62A Resource declaration is a value; give it its own module:
63
64```typescript
65// src/Photos.ts
66import * as Cloudflare from "alchemy/Cloudflare";
67
68export const Photos = Cloudflare.R2.Bucket("Photos");
69```
70
71A declaration is inert until a Stack yields it — importing this
72file deploys nothing.
73
74Workers get the same treatment, with `main: import.meta.url`
75making the file its own entrypoint:
76
77```typescript
78// src/Api.ts
79import * as Cloudflare from "alchemy/Cloudflare";
80import * as Effect from "effect/Effect";
81
82export default class Api extends Cloudflare.Worker<Api>()(
83 "Api",
84 { main: import.meta.url },
85 Effect.gen(function* () {
86 return {
87 fetch: Effect.gen(function* () {
88 // handle requests
89 }),
90 };
91 }),
92) {}
93```
94
95The Worker's declaration and its runtime code live in one file —
96there is no separate `wrangler.toml` pointing at a script.
97
98Layers follow the same rule — the `SandboxLive` Layer lives in
99`src/Sandbox.ts` (see
100[Layers](/infrastructure-as-effects/layers)).
101
102## Group by concern
103
104Resources that change together share a file:
105
106```typescript
107// src/Db.ts
108import * as Drizzle from "alchemy/Drizzle";
109import * as Neon from "alchemy/Neon";
110import * as Effect from "effect/Effect";
111
112export const NeonDb = Effect.gen(function* () {
113 const schema = yield* Drizzle.Schema("app-schema", {
114 schema: "./src/schema.ts",
115 out: "./migrations",
116 });
117
118 const project = yield* Neon.Project("app-db", {
119 region: "aws-us-east-1",
120 });
121
122 const branch = yield* Neon.Branch("app-branch", {
123 project,
124 migrations: schema,
125 });
126
127 return { project, branch, schema };
128});
129```
130
131A migration touches all three — Schema regenerates the SQL,
132Branch applies it — so they share a file. The heuristic: group
133Resources when they share a lifecycle.
134
135## Wrap units in Effects, not in the Stack
136
137`NeonDb` above is an exported `Effect.gen` — a unit of
138composition anything can build on:
139
140```typescript
141// src/Db.ts
142export const Hyperdrive = Effect.gen(function* () {
143 const { branch } = yield* NeonDb;
144 return yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", {
145 origin: branch.origin,
146 });
147});
148```
149
150Both `Hyperdrive` and `alchemy.run.ts` yield `NeonDb`, but each
151Resource is memoized by its logical ID — the database is
152provisioned once no matter how many units yield it.
153
154## Stage-conditional config
155
156When a Resource's props differ by stage, read the stage where the
157Resource is declared:
158
159```typescript
160const { stage } = yield* Alchemy.Stack;
161
162const sandbox = yield* Cloudflare.Container("Sandbox", {
163 instanceType: stage === "prod" ? "standard-1" : "dev",
164});
165```
166
167The decision lives next to the Resource it affects, not in
168`alchemy.run.ts`. The most common case — `pr-*` stages borrowing
169staging's database via `.ref` — is covered in
170[References](/infrastructure-as-code/references).
171
172## alchemy.run.ts is the composition root
173
174With the units in `src/`, the root file shrinks to wiring:
175
176```typescript
177// alchemy.run.ts
178import * as Alchemy from "alchemy";
179import * as Cloudflare from "alchemy/Cloudflare";
180import * as Drizzle from "alchemy/Drizzle";
181import * as Neon from "alchemy/Neon";
182import * as Effect from "effect/Effect";
183import * as Layer from "effect/Layer";
184
185import Api from "./src/Api.ts";
186import { Hyperdrive, NeonDb } from "./src/Db.ts";
187
188export default Alchemy.Stack(
189 "CloudflareNeonDrizzleExample",
190 {
191 providers: Layer.mergeAll(
192 Cloudflare.providers(),
193 Drizzle.providers(),
194 Neon.providers(),
195 ),
196 state: Alchemy.localState(),
197 },
198 Effect.gen(function* () {
199 const { branch } = yield* NeonDb;
200 const hd = yield* Hyperdrive;
201 const api = yield* Api;
202
203 return {
204 url: api.url.as<string>(),
205 branchId: branch.branchId,
206 hyperdriveId: hd.hyperdriveId,
207 };
208 }),
209);
210```
211
212It has four jobs: name the Stack, merge Providers, pick a State
213Store, wire units into outputs. The CLI discovers
214`alchemy.run.ts` at the project root automatically; pass a path
215to override (`alchemy deploy --config ./infra/app.ts`).
216
217If `alchemy.run.ts` is growing, something is missing a home in
218`src/`.
219
220## The shape
221
222```
223.
224├── alchemy.run.ts # composition root — Stack, Providers, State Store, outputs
225├── .alchemy/ # local state — gitignore it
226└── src/
227 ├── Api.ts # Worker — declaration + runtime code
228 ├── Db.ts # database unit — Schema + Project + Branch + Hyperdrive
229 ├── Photos.ts # one-line Resource
230 ├── Sandbox.ts # Container Layer
231 └── schema.ts # plain application code (Drizzle schema)
232```
233
234Tests mirror the tree under `test/`; the tutorial builds this
235shape step by step ([Part 2](/cloudflare/tutorial/part-2)).
236
237## Where next
238
239- [Monorepo](/project-structure/monorepo) — the workspace-level version of
240 this question: one Stack or one per package.
241- [References](/infrastructure-as-code/references) — when the split
242 crosses Stack boundaries.
243- [Layers](/infrastructure-as-effects/layers) — promote
244 a multi-Resource unit into a swappable Layer.
245- [Stacks](/infrastructure-as-code/stack) — the Stack service, stages, and
246 outputs used by the composition root.