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.
When your Alepha app starts, it goes through four phases:
CONFIGURE → START → READY → (running) → STOP
You hook into any of these with $hook.
$hook 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.
Sometimes order matters. You want the database connected before the cache warms up.
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}
Beyond lifecycle hooks, Alepha has a full event system. Think of it as a typed EventEmitter on steroids.
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});
Because alepha.events is fully typed. You get autocomplete. You get compile-time errors if you emit the wrong payload.
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
On the frontend, Alepha emits events during navigation and actions. This is gold for analytics, error tracking, or loading indicators.
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"
Here's a pattern we use in every project:
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.
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};
In NestJS, you implement interfaces:
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:
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.
| 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.