Advanced

Event System

Build reactive applications with MoroJS's powerful event system. Decouple components, enable real-time features, and create scalable architectures.

Overview

Emit and listen to events with one line. Decouple components, enable real-time features, and build reactive architectures.

Emit and listen to events
1import { createApp } from '@morojs/moro';
2
3const app = await createApp();
4const events = app.events;
5
6// Emit events from route handlers
7app.post('/users')
8  .body(z.object({
9    email: z.string().email(),
10    name: z.string()
11  }))
12  .handler(async (req, res) => {
13    const user = await createUser(req.body);
14
15    // Emit user creation event
16    await events.emit('user.created', {
17      user,
18      timestamp: new Date().toISOString()
19    });
20
21    res.json(user);
22  });
23
24// Listeners receive { context, data }
25events.on('user.created', async ({ data }) => {
26  console.log('New user created:', data.user.email);
27  await sendWelcomeEmail(data.user.email);
28  await trackUserSignup(data.user);
29});

Without proper event systems, you're tightly coupling components, blocking requests, and making real-time features difficult. With MoroJS, you get all of that automatically. Traditional event handling requires manual setup and coordination. We handle that automatically.

Without Events

  • Tightly coupled components
  • Blocking request handlers
  • No real-time capabilities
  • Hard to scale and maintain

With MoroJS

  • Decoupled component architecture
  • Asynchronous event processing
  • Built-in real-time support
  • Scalable reactive patterns
Simple event patterns
1// Emit events
2await events.emit('user.created', { user });
3await events.emit('order.completed', { order });
4
5// Listeners receive an EventPayload — { context, data } — so destructure
6// 'data' to reach the value you emitted.
7events.on('user.created', async ({ data }) => {
8  await sendWelcomeEmail(data.user);
9});
10
11events.on('order.completed', async ({ data }) => {
12  await sendConfirmationEmail(data.order);
13});
14
15// One-time listeners
16events.once('app.started', () => {
17  console.log('App started!');
18});

Decoupled

Components communicate through events. No direct dependencies.

Reactive

Build reactive applications with event-driven architecture.

Scalable

Easy to add new listeners. Easy to scale. Easy to maintain.

How It Works

MoroJS includes a built-in event bus that allows components to communicate asynchronously. You emit events from route handlers, modules, or anywhere in your application, and listeners react to those events. This decouples components and enables reactive, scalable architectures.

Basic Event Usage

Event Emitting and Listening
1import { createApp } from '@morojs/moro';
2
3const app = await createApp();
4const events = app.events;
5
6// Emit events from route handlers
7app.post('/users')
8  .body(z.object({
9    email: z.string().email(),
10    name: z.string()
11  }))
12  .handler(async (req, res) => {
13    const user = await createUser(req.body);
14
15    // Emit user creation event
16    await events.emit('user.created', {
17      user,
18      timestamp: new Date().toISOString()
19    });
20
21    res.json(user);
22  });
23
24// Listeners receive { context, data }
25events.on('user.created', async ({ data }) => {
26  console.log('New user created:', data.user.email);
27
28  // Send welcome email
29  await sendWelcomeEmail(data.user.email);
30
31  // Log to analytics
32  await trackUserSignup(data.user);
33});
34
35// One-time event listeners
36events.once('app.started', () => {
37  console.log('Application has started successfully');
38});
39
40// Remove event listeners
41const handler = ({ data }) => console.log('User updated:', data);
42events.on('user.updated', handler);
43events.off('user.updated', handler);

Event System Benefits

  • Decoupled component architecture
  • Asynchronous processing capabilities
  • Real-time feature support
  • Module isolation and communication
  • Scalable reactive patterns

Advanced Event Patterns

Functional Event Handling
1// Functional Event Handling
2export function createUserEventHandlers(services: any) {
3  return {
4    async handleUserCreated(data: { user: User }) {
5      console.log('Handling user creation:', data.user.id);
6      
7      // Send welcome email
8      if (services.emailService) {
9        await services.emailService.sendWelcomeEmail(data.user);
10      }
11      
12      // Create user profile
13      if (services.profileService) {
14        await services.profileService.createUserProfile(data.user);
15      }
16      
17      // Track analytics
18      if (services.analyticsService) {
19        await services.analyticsService.trackSignup(data.user);
20      }
21    },
22    
23    async handleUserUpdated(data: { user: User, changes: Partial<User> }) {
24      console.log('User updated:', data.user.id, data.changes);
25      
26      // Invalidate cache
27      if (services.cacheService) {
28        await services.cacheService.invalidateUserCache(data.user.id);
29      }
30      
31      // Notify connected clients
32      if (services.notificationService) {
33        await services.notificationService.notifyUserUpdate(data.user);
34      }
35    },
36    
37    async handleUserDeleted(data: { userId: string }) {
38      console.log('User deleted:', data.userId);
39      
40      // Clean up user data
41      if (services.cleanupService) {
42        await services.cleanupService.cleanupUserData(data.userId);
43      }
44      
45      // Archive user information
46      if (services.archiveService) {
47        await services.archiveService.archiveUser(data.userId);
48      }
49    }
50  };
51}
Event Processing Pipeline
1import { createApp, createFrameworkLogger } from '@morojs/moro';
2
3const app = await createApp();
4const events = app.events;
5const log = createFrameworkLogger('Events');
6
7// The bus has no middleware chain. For cross-cutting concerns, wrap emit:
8const emit = async (name, data) => {
9  log.debug('Event emitted', 'Bus', { name });
10  return events.emit(name, data);
11};
12
13// Built-in introspection
14events.enableAuditLog();
15const recent = events.getAuditLog();   // recent emissions
16const metrics = events.getMetrics();   // counts and timings
17
18// Per-module buses keep namespaces isolated
19const userBus = events.createModuleBus('users');
20userBus.on('created', ({ data }) => sendWelcomeEmail(data.user));
21await userBus.emit('created', { user });
22
23// Background work: schedule it with app.job (requires jobs.enabled)
24app.job('rebuild-recommendations', '15m', async () => {
25  await generateUserRecommendations();
26});

Next Steps