alepha@docs:~/docs/guides/core$
cat 0-alepha-instance.md | pretty
3 min read
Last commit:

#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:

typescript
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:

typescript
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.env gives raw access, use the $env primitive 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:

typescript
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 $route or $repository in 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):

typescript
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:

typescript
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)
typescript
1alepha.inject(Logger, { lifetime: "transient" });

#Lifecycle

Alepha has a strict lifecycle to ensure resources are opened and closed correctly:

bash
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:

typescript
1import { run } from "alepha";2 3run(alepha);

#Manual Control (Tests)

In tests, you often want manual lifecycle control:

typescript
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.

typescript
 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:

typescript
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:

typescript
 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:

typescript
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.