#The Alepha Instance
The Alepha class is the central container of your application. It holds configuration, manages state, controls lifecycle, and wires dependencies together.
Unlike tools that rely on global side-effects, Alepha keeps everything contained within this instance. This makes your application portable, testable, and predictable.
#Creating the Instance
Use the static factory method to initialize your app:
1import { Alepha } from "alepha";2 3const alepha = Alepha.create();
create() automatically merges process.env with any custom configuration you provide. In test environments (Vitest with globals: true), it hooks into beforeAll and afterAll to manage the app lifecycle automatically.
#Configuration
Pass a configuration object to create. The most important property is env:
1const alepha = Alepha.create({2 env: {3 APP_NAME: "My SaaS",4 // Secrets from process.env are merged automatically5 },6});7 8alepha.env.APP_NAME; // "My SaaS"
Typed Environment Variables
While
alepha.envgives raw access, use the$envprimitive inside services for type-safe, validated environment variables.
#Dependency Injection
Alepha is built on a dependency injection container. You register classes and Alepha wires them together.
#Registering Services (.with)
Use .with() to add services, modules, or providers:
1import { Alepha, run } from "alepha";2import { AlephaServer } from "alepha/server";3 4const alepha = Alepha.create();5alepha.with(AlephaServer);6alepha.with(MyService);
Auto-Registration
You rarely need to register core modules manually. If you use a primitive like
$routeor$repositoryin your class, Alepha detects the dependency and registers the necessary modules for you.
#Injecting Services (.inject)
Access a service instance from outside a class (e.g., in a script or test):
1const myService = alepha.inject(MyService);2myService.doSomething();
.inject(MyService) is like new MyService() but with all dependencies automatically resolved.
#Swapping Implementations
Replace a service with a different implementation. This is the core of Alepha's testing and environment strategy:
1// In production: real emails via SMTP2// In development: log to console3alepha.with({4 provide: EmailProvider,5 use: process.env.NODE_ENV === "production"6 ? SmtpEmailProvider7 : ConsoleEmailProvider,8});
Any service that injects EmailProvider will receive the substituted implementation. Your business logic doesn't change.
#Service Lifetimes
| Lifetime | Behavior |
|---|---|
singleton |
One instance per Alepha runtime (default) |
transient |
New instance every time |
scoped |
One instance per request context (AsyncLocalStorage) |
1alepha.inject(Logger, { lifetime: "transient" });
#Lifecycle
Alepha has a strict lifecycle to ensure resources are opened and closed correctly:
configure → start → ready → (running) → stop
| Phase | What happens |
|---|---|
configure |
Services register configuration. Primitives read their schemas. |
start |
Providers connect to I/O (database, HTTP server). |
ready |
App is live. Background jobs and schedulers start. |
stop |
Graceful shutdown. Connections close, buffers flush. |
#Running the App
Use the run helper. It configures, starts, and handles SIGTERM/SIGINT for graceful shutdown:
1import { run } from "alepha";2 3run(alepha);
#Manual Control (Tests)
In tests, you often want manual lifecycle control:
1test("MyService", async () => {2 const app = Alepha.create().with(MyService);3 await app.start();4 5 const service = app.inject(MyService);6 expect(service.isReady).toBe(true);7 8 // await app.stop(); ← automatically called by Vitest hooks9});
#Providers
Providers separate infrastructure logic (the "how") from business logic (the "what"). A provider wraps external systems — databases, email services, storage — behind a clean API.
1import { $env, $hook, t } from "alepha"; 2import { $logger } from "alepha/logger"; 3 4export class EmailProvider { 5 log = $logger(); 6 7 env = $env(t.object({ 8 SMTP_HOST: t.text(), 9 SMTP_USER: t.text(),10 SMTP_PASS: t.text(),11 }));12 13 protected transporter = createTransport({14 host: this.env.SMTP_HOST,15 auth: { user: this.env.SMTP_USER, pass: this.env.SMTP_PASS },16 });17 18 onStart = $hook({19 on: "start",20 handler: async () => {21 await this.transporter.verify();22 this.log.info("Connected to SMTP");23 },24 });25 26 async send(to: string, subject: string) {27 return this.transporter.sendMail({ from: "[email protected]", to, subject });28 }29}
Injecting a provider is no different from injecting any other service:
1class UserService {2 email = $inject(EmailProvider);3 4 register = $action({5 handler: async ({ body }) => {6 await this.email.send(body.email, "Welcome!");7 },8 });9}
#The Swap Strategy
Define an abstract provider, then swap implementations by environment:
1// Abstract contract 2export abstract class QueueProvider { 3 abstract push(job: object): Promise<void>; 4} 5 6// Production: Redis 7export class RedisQueueProvider extends QueueProvider { 8 async push(job: object) { /* redis logic */ } 9}10 11// Dev/Test: in-memory12export class MemoryQueueProvider extends QueueProvider {13 queue: object[] = [];14 async push(job: object) { this.queue.push(job); }15}
Wire it up:
1alepha.with({2 provide: QueueProvider,3 use: alepha.isProduction() ? RedisQueueProvider : MemoryQueueProvider,4});
Alepha ships with standard providers for common needs: file storage (alepha/bucket), job queues (alepha/queue), logging (alepha/logger), caching (alepha/cache), and more. Each has memory implementations for testing.