alepha@docs:~/docs/concepts$
cat 8-events-and-hooks.md
2 min read
Last commit:

#Events & Hooks

If you've ever used Express middleware or NestJS lifecycle hooks, you know how painful it can be to coordinate "when things happen" in your application.

Express gives you app.use() chains that execute in order. NestJS has OnModuleInit, OnApplicationBootstrap, and a dozen other interfaces to implement. Both approaches scatter your initialization logic across multiple files.

Alepha takes a different approach: everything is an event.

#The Lifecycle

When your Alepha app starts, it goes through four phases:

bash
CONFIGURE  →  START  →  READY  →  (running)  →  STOP
  • Configure: Schemas are validated, primitives are registered
  • Start: Database connections open, HTTP server binds to port
  • Ready: App is live, schedulers and queues begin processing
  • Stop: Graceful shutdown, connections close

You hook into any of these with $hook.

#Using $hook

typescript
 1import { $hook } from "alepha"; 2import { $logger } from "alepha/logger"; 3  4class DatabaseService { 5  log = $logger(); 6  7  // runs when the app starts 8  onStart = $hook({ 9    on: "start",10    handler: async () => {11      await this.connect();12      this.log.info("Database connected");13    }14  });15 16  // runs during graceful shutdown17  onStop = $hook({18    on: "stop",19    handler: async () => {20      await this.disconnect();21    }22  });23}

No interfaces to implement. No decorators to remember. Just declare what you need, where you need it.

#Hook Priority

Sometimes order matters. You want the database connected before the cache warms up.

typescript
 1class CacheService { 2  warmCache = $hook({ 3    on: "start", 4    priority: 10, // higher = runs later 5    handler: async () => { 6      // database is already connected here 7      await this.preloadFrequentData(); 8    } 9  });10}

#The Event Bus

Beyond lifecycle hooks, Alepha has a full event system. Think of it as a typed EventEmitter on steroids.

typescript
1// emit an event2alepha.events.emit("user:created", { userId: "123" });3 4// listen to an event5alepha.events.on("user:created", ({ userId }) => {6  console.log(`New user: ${userId}`);7});

#Why Not Just Use EventEmitter?

Because alepha.events is fully typed. You get autocomplete. You get compile-time errors if you emit the wrong payload.

typescript
 1// extend the Hooks interface for custom events 2declare module "alepha" { 3  interface Hooks { 4    "user:created": { userId: string }; 5    "order:completed": { orderId: string; total: number }; 6  } 7} 8  9// now TypeScript knows the payload shape10alepha.events.emit("user:created", { userId: "abc" }); // ok11alepha.events.emit("user:created", { wrong: "key" });  // type error

#React Events

On the frontend, Alepha emits events during navigation and actions. This is gold for analytics, error tracking, or loading indicators.

typescript
 1// available events 2"react:action:begin"      // any user action started 3"react:action:success"    // action completed 4"react:action:error"      // action failed 5"react:action:end"        // action finished (success or error) 6  7"react:transition:begin"  // page navigation started 8"react:transition:success" 9"react:transition:error"10"react:transition:end"11 12"form:submit:begin"       // form submission13"form:submit:success"14"form:submit:error"15"form:submit:end"

#Global Error Toast

Here's a pattern we use in every project:

typescript
1// somewhere in your app initialization2alepha.events.on("react:action:error", ({ error }) => {3  toast.error(error.message);4 5  // send to Sentry, LogRocket, etc.6  Sentry.captureException(error);7});

One listener. Every error in your app gets a toast and gets tracked. No try/catch spaghetti.

#Loading Indicators

typescript
 1const App = () => { 2  const [loading, setLoading] = useState(false); 3  4  useEvents({ 5    "react:transition:begin": () => setLoading(true), 6    "react:transition:end": () => setLoading(false), 7  }, []); 8  9  return (10    <>11      {loading && <TopProgressBar />}12      <Outlet />13    </>14  );15};

#Comparison: NestJS vs Alepha

In NestJS, you implement interfaces:

typescript
 1// nestjs way - scattered across the class 2@Injectable() 3export class DatabaseService implements OnModuleInit, OnApplicationShutdown { 4  async onModuleInit() { 5    await this.connect(); 6  } 7  8  async onApplicationShutdown() { 9    await this.disconnect();10  }11}

In Alepha, hooks are explicit properties:

typescript
1// alepha way - hooks are visible, named, self-documenting2class DatabaseService {3  connectOnStart = $hook({ on: "start", handler: () => this.connect() });4  disconnectOnStop = $hook({ on: "stop", handler: () => this.disconnect() });5}

The Alepha way is more verbose, sure. But when you're debugging "why isn't this running?", you can actually see the hooks. They have names. They show up in logs.

#When To Use What

Need Solution
Run code at app startup/shutdown $hook({ on: "start" })
Communicate between services alepha.events.emit()
React to user actions in UI useEvents() hook
Add analytics/error tracking Listen to react:action:* events

Events and hooks are the nervous system of your Alepha app. Once you start using them, you'll wonder how you ever lived without typed events.

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