Skip to content
Back to skills

Create Event Handlers

ASecurity

Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.

  • 57 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 17, 2026
documentationtypescriptazuredocumentation

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill create-event-handlers --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Create Event Handlers?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Create Event Handlers
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-create-event-handlers/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-create-event-handlers)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: create-event-handlers
description: Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.
---

# Create Event Handlers

Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9.

## Event Naming Convention

Reference: ModuleImplementationGuide.md Section 9.1

Format: `{domain}.{entity}.{action}`

✅ **Correct:**
- `user.created`
- `auth.login.success`
- `notification.email.sent`
- `secret.rotated`

❌ **Wrong:**
- `userCreated`
- `loginSuccess`
- `emailSent`

**Standard Actions:**
- `created`, `updated`, `deleted`
- `started`, `completed`, `failed`
- `sent`, `received`, `expired`, `rotated`

## Event Structure

Reference: ModuleImplementationGuide.md Section 9.2

```typescript
interface DomainEvent<T = unknown> {
  // Identity
  id: string;                    // Unique event ID (UUID)
  type: string;                  // Event type (domain.entity.action)
  
  // Metadata
  timestamp: string;             // ISO 8601
  version: string;               // Event schema version
  source: string;                // Module that emitted
  correlationId?: string;        // Request correlation
  
  // Context
  tenantId?: string;             // Tenant context (PREFERRED; use for new modules)
  organizationId?: string;       // DEPRECATED for new modules; prefer tenantId
  userId?: string;               // Actor
  
  // Payload
  data: T;                       // Event-specific data
}
```

## Event Publisher

Reference: containers/auth/src/events/publishers/AuthEventPublisher.ts

### src/events/publishers/[Module]EventPublisher.ts

```typescript
import { randomUUID } from 'crypto';
import { EventPublisher, getChannel, closeConnection } from '@coder/shared';
import { log } from '../../utils/logger';
import { getConfig } from '../../config';

let publisher: EventPublisher | null = null;

export async function initializeEventPublisher(): Promise<void> {
  if (publisher) return;
  
  const config = getConfig();
  if (!config.rabbitmq?.url) {
    log.warn('RabbitMQ URL not configured, events will not be published');
    return;
  }
  
  try {
    await getChannel();
    publisher = new EventPublisher(config.rabbitmq.exchange || 'coder_events');
    log.info('Event publisher initialized', { exchange: config.rabbitmq.exchange });
  } catch (error: any) {
    log.error('Failed to initialize event publisher', error);
  }
}

export async function closeEventPublisher(): Promise<void> {
  try {
    await closeConnection();
    publisher = null;
  } catch (error: any) {
    log.error('Error closing event publisher', error);
  }
}

function getPublisher(): EventPublisher | null {
  if (!publisher) {
    const config = getConfig();
    publisher = new EventPublisher(config.rabbitmq.exchange || 'coder_events');
  }
  return publisher;
}

export function createBaseEvent(
  type: string,
  userId?: string,
  tenantId?: string,
  correlationId?: string,
  data?: any
) {
  return {
    id: randomUUID(),
    type,
    timestamp: new Date().toISOString(),
    version: '1.0',
    source: '[module-name]',
    correlationId,
    tenantId,
    userId,
    data: data || {},
  };
}

export async function publishEvent(event: any, routingKey?: string): Promise<void> {
  const pub = getPublisher();
  if (!pub) {
    log.warn('Event publisher not initialized, skipping event', { type: event.type });
    return;
  }
  
  try {
    await pub.publish(routingKey || event.type, event);
    log.debug('Event published', { type: event.type, id: event.id });
  } catch (error: any) {
    log.error('Failed to publish event', error, { type: event.type });
  }
}
```

### Usage in Services

```typescript
import { publishEvent, createBaseEvent } from '../events/publishers/ModuleEventPublisher';

// Publish event (tenantId for tenant context)
const event = createBaseEvent(
  'resource.created',
  userId,
  tenantId,
  correlationId,
  {
    resourceId: resource.id,
    name: resource.name,
  }
);

await publishEvent(event);
```

## Event Consumer

Reference: ModuleImplementationGuide.md Section 9.4

### src/events/consumers/[Resource]Consumer.ts

```typescript
import { EventConsumer } from '@coder/shared';
import { log } from '../../utils/logger';
import { getConfig } from '../../config';

let consumer: EventConsumer | null = null;

export async function initializeEventConsumer(): Promise<void> {
  if (consumer) return;
  
  const config = getConfig();
  if (!config.rabbitmq?.url) {
    log.warn('RabbitMQ URL not configured, events will not be consumed');
    return;
  }
  
  try {
    consumer = new EventConsumer({
      queue: config.rabbitmq.queue || '[module-name]_service',
      exchange: config.rabbitmq.exchange || 'coder_events',
      bindings: config.rabbitmq.bindings || [],
    });
    
    // Register handlers
    consumer.on('other.resource.created', handleResourceCreated);
    consumer.on('other.resource.updated', handleResourceUpdated);
    
    await consumer.start();
    log.info('Event consumer initialized', { queue: config.rabbitmq.queue });
  } catch (error: any) {
    log.error('Failed to initialize event consumer', error);
  }
}

async function handleResourceCreated(event: any): Promise<void> {
  log.info('Resource created event received', { resourceId: event.data.resourceId });
  // Handle the event
}

async function handleResourceUpdated(event: any): Promise<void> {
  log.info('Resource updated event received', { resourceId: event.data.resourceId });
  // Handle the event
}

export async function closeEventConsumer(): Promise<void> {
  try {
    if (consumer) {
      await consumer.stop();
      consumer = null;
    }
  } catch (error: any) {
    log.error('Error closing event consumer', error);
  }
}
```

## Event Documentation

Reference: ModuleImplementationGuide.md Section 9.5

### logs-events.md (if events are logged)

Create in module root if module publishes events that get logged:

```markdown
# [Module Name] - Logs Events

## Published Events

### {domain}.{entity}.{action}

**Description**: When this event is triggered.

**Triggered When**: 
- Condition 1
- Condition 2

**Event Type**: `{domain}.{entity}.{action}`

**Event Schema**:

\`\`\`json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["id", "type", "timestamp", "version", "source", "data"],
  "properties": {
    "id": { "type": "string", "format": "uuid" },
    "type": { "type": "string" },
    "timestamp": { "type": "string", "format": "date-time" },
    "version": { "type": "string" },
    "source": { "type": "string" },
    "tenantId": { "type": "string", "format": "uuid" },
    "userId": { "type": "string", "format": "uuid" },
    "data": {
      "type": "object",
      "properties": {
        "resourceId": { "type": "string" }
      }
    }
  }
}
\`\`\`
```

### notifications-events.md (if events trigger notifications)

Create in module root if module publishes events that trigger notifications.

## Configuration

Add to config/default.yaml:

```yaml
rabbitmq:
  url: ${RABBITMQ_URL}
  exchange: coder_events
  queue: [module-name]_service
  bindings:
    - "other.resource.created"
    - "other.resource.updated"
```

## Checklist

- [ ] Event publisher created following pattern
- [ ] Event consumer created (if consuming events)
- [ ] Events follow naming convention: {domain}.{entity}.{action}
- [ ] Events include all required fields (id, type, version, timestamp, source, data)
- [ ] Events include tenantId when applicable (RabbitMQ only; no Azure Service Bus)
- [ ] logs-events.md created (if events are logged)
- [ ] notifications-events.md created (if events trigger notifications)
- [ ] RabbitMQ config added to default.yaml

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.