alepha@docs:~/docs/concepts$
cat 3-providers.md
2 min read
Last commit:

#Providers

If Primitives are the "superpowers" you attach to classes, Providers are the engines that power them.

In strict architectural terms, a Service contains your Business Logic (the "What"), and a Provider contains the Infrastructure Logic (the "How").

#The Separation of Concerns

When writing an application, you often need to talk to the outside world: sending emails, connecting to Redis, uploading files to S3.

You could put this logic directly inside your User Service, but that makes testing a nightmare. Instead, Alepha encourages you to wrap this infrastructure code into a Provider.

At the end, providers are just a convention to make mocking and swapping implementations easier.

#Anatomy of a Provider

A Provider is just a class. However, it typically does three things:

  1. Validates its own configuration via $env.
  2. Manages its connection lifecycle via $hook (connect/disconnect).
  3. Exposes a clean API to the rest of the app.

Here is a production-ready Email Provider:

ts
 1import { $env, $hook, t } from "alepha"; 2import { $logger } from "alepha/logger"; 3import { createTransport, type Transporter } from "my-super-mailer-lib"; 4  5export class EmailProvider { 6  log = $logger(); 7  8  // 1. Configuration: The provider validates its own requirements 9  env = $env(t.object({10    SMTP_HOST: t.text(),11    SMTP_USER: t.text(),12    SMTP_PASS: t.text(),13  }));14 15  transporter = createTransport({16    host: this.env.SMTP_HOST,17    auth: { user: this.env.SMTP_USER, pass: this.env.SMTP_PASS }18  });19 20  // 2. Lifecycle: Connect when the app starts21  onStart = $hook({22    on: "start",23    handler: async () => {24      await this.transporter.verify();25      this.log.info("Connected to SMTP");26    }27  });28 29  // 3. Public API: The rest of your app uses this30  async send(to: string, subject: string) {31    return this.transporter.sendMail({ from: "me@app.com", to, subject });32  }33}

#Using a Provider

Injecting a provider is no different than injecting a service. Use $inject.

ts
 1import { $inject, $action } from "alepha"; 2import { EmailProvider } from "./EmailProvider"; 3  4class UserService { 5  // Alepha injects the singleton instance of EmailProvider 6  email = $inject(EmailProvider); 7  8  register = $action({ 9    handler: async ({ body }) => {10      // ... logic to create user ...11      await this.email.send(body.email, "Welcome!");12    }13  });14}

#Polymorphism (The "Swap" Strategy)

This is where Alepha shines.

Sometimes, you want the "What" to stay the same, but the "How" to change depending on where the app is running.

  • Local Dev: You want to log emails to the console (free, fast).
  • Production: You want to send real emails via SendGrid/AWS SES.

You can achieve this using Service Substitution.

#1. Define the Abstract Provider

This defines the "Contract". Your services will depend on this.

ts
1export abstract class QueueProvider {2  abstract push(job: object): Promise<void>;3}

#2. Define Implementations

Different ways to fulfill the contract.

ts
 1// For Production: Real Redis 2export class RedisQueueProvider extends QueueProvider { 3  async push(job: object) { /* ... redis logic ... */ } 4} 5  6// For Dev/Test: Just an array in memory 7export class MemoryQueueProvider extends QueueProvider { 8  queue: object[] = []; 9  async push(job: object) { this.queue.push(job); }10}

#3. Wire it up

In your entry point, you tell Alepha which implementation to use based on the environment.

ts
 1import { Alepha, run } from "alepha"; 2import { QueueProvider, RedisQueueProvider, MemoryQueueProvider } from "./queue"; 3  4const alepha = Alepha.create(); 5  6// The Magic: Dependency Injection wiring 7if (alepha.isProduction()) { 8  // In Prod: When someone asks for 'QueueProvider', give them 'RedisQueueProvider' 9  alepha.with({ provide: QueueProvider, use: RedisQueueProvider });10} else {11  // In Dev: When someone asks for 'QueueProvider', give them 'MemoryQueueProvider'12  alepha.with({ provide: QueueProvider, use: MemoryQueueProvider });13}14 15// Your app logic doesn't care. It just injects 'QueueProvider'.16run(alepha);

Voilà, now you are an Alepha EXPERT.

#Built-in Providers

Alepha provides standard implementations for common needs so you don't have to reinvent the wheel.

  • alepha/bucket: File storage. Switches between LocalFileStorageProvider (disk) and S3/Azure automatically.
  • alepha/queue: Job queues. Switches between MemoryQueue and RedisQueue.
  • alepha/logger: Logging. Switches between Console (pretty colors) and JSON (for log aggregators).

You focus on the logic; Alepha handles the plumbing.

On This Page
No headings found...
ready
mainTypeScript
UTF-8concepts_providers.md