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:
// Emit once from the code that owns the action.
await this.squid.events().emit({ id: generateUUID(), type: 'user.signed-up', payload: { userId } });
// 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 Case | Recommendation |
|---|---|
| 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 changes | Use Triggers |
| Call a function from the client | Use Executables |
| Run code on a schedule | Use Schedulers |
How it works
- Trusted backend code calls
squid.events().emit()with aTriggerEvent(an{ id, type, payload }object). - Squid enqueues the event on core's internal event stream.
emit()resolves once the event is enqueued, not once it has been handled. - Every method decorated with
@eventHandler(type)for the sametypereceives the event. - 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
@eventHandlerfor 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/backendpackage installed from NPM
Step 1: Declare an event handler
Create a service class that extends SquidService and decorate a method with @eventHandler:
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():
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:
| Property | Type | Description |
|---|---|---|
id | string | Unique event id. Handlers can use it to de-duplicate redelivered events. |
type | string | The event type. Determines which @eventHandler subscribers receive it. |
payload | T | Arbitrary, 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The 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:
@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
-
Design handlers for idempotency. Because delivery is at-least-once, a handler can run more than once for the same event. Use
event.idto detect and skip work you have already done. -
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. -
Keep payloads small and JSON-serializable. Send identifiers and let subscribers load what they need, rather than embedding large objects in the payload.
-
Namespace your event types. Names such as
order.placedoruser.signed-upkeep types unambiguous as the number of subscribers grows.
See Also
- Queue message handlers - Consume messages from a queue topic
- Triggers - React to database changes
- Executables - Call backend functions from the client
- Schedulers - Run code on a schedule