Elysia
The shared Elysia app — routes, auth mount, OpenAPI, telemetry — plus the type-safe Eden Treaty client the web app consumes.
@halostack/elysia is the heart of the API. It holds the composed Elysia app — the routes, the mounted auth handler, OpenAPI, and telemetry — together with the type-safe Eden Treaty client the web app uses to call it. The deployable apps/api is just a thin runtime shell around this package, which is where all the actual API surface lives.
Importing
The package exposes the server instance and the browser-safe client separately:
The split matters for bundle safety: client.ts imports App as a type-only import, so it carries no server runtime and is safe to ship in the browser bundle while still being fully type-synced to the server.
Module structure
Routes are organized per feature under src/modules/<feature>/, and each module is composed onto the app in src/app.ts with .use(...). Every feature follows the same internal split, which keeps validation, routing, and logic cleanly separated:
*-model.ts— drizzle-typebox request/response schemas, which double as both validation and OpenAPI definitions.*-routes.ts— the Elysia route group.*-service.ts/*-queries.ts— the business logic and database access.
The current modules are auth, health, notifications, storage, and todos, plus a shared module for cross-cutting concerns like pagination and filters. Beyond the feature modules, app.ts also mounts better-auth (via auth.handler), CORS (credentialed and pinned to WEB_URL), and OpenAPI.
Conventions
Adding an endpoint means adding or extending a module here — and because the Eden client is derived from the app's types, it updates for free with no separate client code to maintain.
Validate inputs and outputs with drizzle-typebox models. When a TypeBox override is unavoidable, apply a single override at the model boundary rather than scattering casts — and never reach for as any. The package is strict TypeScript throughout, and tests are co-located as *.test.ts beside the routes, run with bun test src/.
For exploring the API, the @elysia/openapi plugin serves a live, interactive reference (Scalar) at /openapi, with the raw JSON at /openapi/json. It's reflected straight from the routes and models, so there's no build step and no committed spec to keep in sync.