Directory Structure
Recommended project layout for Gello applications
Standard Layout
src/
├── main.ts # Entry point — Layer.launch(MainLayer)
├── layers/
│ ├── index.ts # Export AppLayer (merged layers)
│ ├── Config.ts # Config Layer with Effect.Config
│ ├── Database.ts # PgPool + Drizzle Layers
│ └── Redis.ts # Redis Layer with acquireRelease
├── services/
│ ├── UserRepo.ts # Repository Layers
│ ├── PostRepo.ts
│ └── MailService.ts # External service integrations
├── routes/
│ ├── index.ts # Export all routes
│ ├── users.ts # User route handlers
│ └── posts.ts # Post route handlers
├── jobs/
│ ├── SendEmail.ts # Job definitions
│ └── ProcessUpload.ts
├── schemas/
│ ├── User.ts # @effect/schema definitions
│ └── Post.ts
└── lib/
├── db/
│ └── schema.ts # Drizzle schema definitions
└── errors.ts # Tagged error classesEntry Point
// src/main.ts
import { pipe } from "effect"
import * as Layer from "effect/Layer"
import * as HttpServer from "@effect/platform/HttpServer"
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import { createServer } from "node:http"
import { AppRouter } from "./routes"
import { AppLayer } from "./layers"
const MainLayer = pipe(
HttpServer.serve(AppRouter),
HttpServer.withLogAddress,
Layer.provide(AppLayer),
Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)
Layer.launch(MainLayer).pipe(NodeRuntime.runMain)Layers Directory
// src/layers/index.ts
import { Layer } from "effect"
import { ConfigLive } from "./Config"
import { PgPoolLive, DbLive } from "./Database"
import { RedisLive } from "./Redis"
import { UserRepoLive } from "../services/UserRepo"
export const AppLayer = Layer.mergeAll(
ConfigLive,
PgPoolLive,
DbLive,
RedisLive,
UserRepoLive
)Services Directory
Each service is a Context.Tag with a Layer implementation.
// src/services/UserRepo.ts
import { Context, Effect, Layer } from "effect"
import { Db } from "../layers/Database"
interface UserRepoService {
findById: (id: string) => Effect.Effect<User | null>
create: (data: CreateUser) => Effect.Effect<User>
list: (opts: ListOpts) => Effect.Effect<User[]>
}
export class UserRepo extends Context.Tag("UserRepo")<
UserRepo,
UserRepoService
>() {}
export const UserRepoLive = Layer.effect(
UserRepo,
Effect.gen(function* () {
const db = yield* Db
return {
findById: (id) => Effect.tryPromise(() => /* ... */),
create: (data) => Effect.tryPromise(() => /* ... */),
list: (opts) => Effect.tryPromise(() => /* ... */)
}
})
)Routes Directory
// src/routes/index.ts
import { route } from "@gello/core-adapters-node"
import { userRoutes } from "./users"
import { postRoutes } from "./posts"
const apiInfo = Effect.succeed(
HttpServerResponse.json({ name: "API", version: "1.0.0" })
)
export const routes = [
route.get("/", apiInfo),
route.get("/health", Effect.succeed(HttpServerResponse.json({ status: "ok" }))),
...userRoutes,
...postRoutes,
] as constSchemas Directory
// src/schemas/User.ts
import * as S from "@effect/schema/Schema"
export const CreateUser = S.Struct({
name: S.String.pipe(S.minLength(2)),
email: S.String.pipe(S.pattern(/@/))
})
export const UpdateUser = S.partial(CreateUser)
export const User = S.Struct({
id: S.String,
name: S.String,
email: S.String,
createdAt: S.Date
})
export type CreateUser = S.Schema.Type<typeof CreateUser>
export type User = S.Schema.Type<typeof User>Worker Entry Point
// src/worker.ts
import { pipe } from "effect"
import * as Layer from "effect/Layer"
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import { AppLayer } from "./layers"
import { EmailWorker } from "./jobs/SendEmail"
const WorkerLayer = pipe(
EmailWorker,
Layer.provide(AppLayer)
)
Layer.launch(WorkerLayer).pipe(NodeRuntime.runMain)