Skip to main content

Events

Broadcast an event from trusted backend code to one or more backend handlers.

Why Use Events

A single backend action often needs to trigger several independent follow-ups. A new signup, for example, might need to send a welcome email, provision resources, and update analytics. Wiring all of that into the signup function couples unrelated concerns and makes each one harder to change.

With events, the action emits one event and each concern subscribes independently:

Backend code
// Emit once from the code that owns the action.
await this.squid.events().emit({ id: generateUUID(), type: 'user.signed-up', payload: { userId } });
Backend code
// Handle it in as many independent subscribers as you need.
@eventHandler<{ userId: string }>('user.signed-up')
async sendWelcomeEmail(event: TriggerEvent<{ userId: string }>): Promise<void> {
await this.emailWelcome(event.payload.userId);
}

Overview

Events are a lightweight publish/subscribe mechanism inside your Squid backend. Trusted server-side code emits a typed event with squid.events().emit(), and every backend method decorated with @eventHandler for that event type receives a copy. Events travel over Squid core's internal event stream, so they do not require a queue connector to be configured.

When to use events

Use CaseRecommendation
Fan a single server-side action out to multiple independent handlers✅ Events
Process messages published to a queue topic (including from clients)Use Queue message handlers
React to database changesUse Triggers
Call a function from the clientUse Executables
Run code on a scheduleUse Schedulers

How it works

  1. Trusted backend code calls squid.events().emit() with a TriggerEvent (an { id, type, payload } object).
  2. Squid enqueues the event on core's internal event stream. emit() resolves once the event is enqueued, not once it has been handled.
  3. Every method decorated with @eventHandler(type) for the same type receives the event.
  4. Delivery is at-least-once with no ordering guarantee, so a handler may occasionally run more than once and events of the same type may arrive out of order.

Events vs queue message handlers

Both deliver messages to backend handlers, but they solve different problems:

  • Events broadcast to every @eventHandler for a type and run over Squid's internal event stream, with no queue connector to configure. Emitting requires API key authentication, so events always originate from trusted backend code.
  • Queue message handlers consume messages from a named queue topic (the built-in queue or an external connector such as Kafka) and can be produced from the client.

Quick Start

Prerequisites

  • A Squid backend project initialized with squid init
  • The @squidcloud/backend package installed from NPM

Step 1: Declare an event handler

Create a service class that extends SquidService and decorate a method with @eventHandler:

Backend code
import { SquidService, eventHandler, TriggerEvent } from '@squidcloud/backend';

interface OrderPlaced {
orderId: string;
customerEmail: string;
}

export class ExampleService extends SquidService {
// Runs once for every 'order.placed' event that is emitted.
@eventHandler<OrderPlaced>('order.placed')
async onOrderPlaced(event: TriggerEvent<OrderPlaced>): Promise<void> {
const { orderId, customerEmail } = event.payload;
// Email the customer using your own email helper.
await this.sendOrderConfirmationEmail(customerEmail, orderId);
}
}

Step 2: Emit an event from trusted backend code

Emitting requires API key authentication, so it must run in backend code and never on the client, which would leak the API key. Use this.squid to access the backend Squid instance and call events().emit():

Backend code
import { SquidService, executable, TriggerEvent } from '@squidcloud/backend';
import { generateUUID } from '@squidcloud/client';

export class ExampleService extends SquidService {
@executable()
async placeOrder(orderId: string, customerEmail: string): Promise<void> {
// Persist the order first, then announce it.
const event: TriggerEvent<OrderPlaced> = {
id: generateUUID(), // Unique per event; used to trace and de-duplicate.
type: 'order.placed',
payload: { orderId, customerEmail },
};
await this.squid.events().emit(event);
}
}

Step 3: Start or deploy the backend

For local development, run the backend locally using the Squid CLI:

squid start

To deploy to the cloud, see deploying your backend.

Step 4: Verify

Trigger the emit (for example by calling the executable above) and check the Squid Console logs to confirm the handler was invoked.

Core Concepts

The TriggerEvent object

Every event is a TriggerEvent with three fields:

PropertyTypeDescription
idstringUnique event id. Handlers can use it to de-duplicate redelivered events.
typestringThe event type. Determines which @eventHandler subscribers receive it.
payloadTArbitrary, JSON-serializable event payload.

The @eventHandler decorator

@eventHandler<T>(type) marks a SquidService method as a subscriber for events of the given type. The method receives the full TriggerEvent<T>. Multiple methods, in the same or different services, may subscribe to the same type, and each receives its own copy.

ParameterTypeRequiredDescription
typestringYesThe event type to subscribe to.

Emitting events

squid.events().emit(event) publishes a TriggerEvent. It requires API key authentication and therefore runs only in backend code. The returned promise resolves once the event is enqueued, not once subscribers have processed it.

Delivery semantics

Events are delivered at-least-once with no ordering guarantee. A handler may occasionally receive the same event more than once, and events of the same type may arrive out of order. Design handlers to be idempotent and order-independent.

Error Handling

If an event handler throws, the error is logged in the Squid Console. Because delivery is at-least-once, an event may be redelivered. Wrap handler logic in try/catch and make retries safe:

Backend code
@eventHandler<OrderPlaced>('order.placed')
async onOrderPlaced(event: TriggerEvent<OrderPlaced>): Promise<void> {
try {
await this.processOrder(event.payload);
} catch (error) {
// event.id identifies the specific event across redeliveries.
console.error(`Failed to handle order event ${event.id}:`, error);
throw error;
}
}

Best Practices

  1. Design handlers for idempotency. Because delivery is at-least-once, a handler can run more than once for the same event. Use event.id to detect and skip work you have already done.

  2. Emit only from backend code. emit() requires an API key, so trigger it from executables or other backend handlers rather than the client, which would leak the key.

  3. Keep payloads small and JSON-serializable. Send identifiers and let subscribers load what they need, rather than embedding large objects in the payload.

  4. Namespace your event types. Names such as order.placed or user.signed-up keep types unambiguous as the number of subscribers grows.

See Also