Skip to main content
Echoes in Orionjs provide a structured way to implement event-driven architecture and handle inter-service communication. Using the @Echoes() and @EchoRequest() or @EchoEvent() decorators, you can easily create handlers for both synchronous requests and asynchronous events.

Standalone core and Orion integration

@orion-js/echoes is framework-independent. Its required dependency graph does not include @orion-js/schema, @orion-js/services, @orion-js/http, @orion-js/logger, or any other Orion runtime package. Orion applications keep their existing behavior through @orion-js/echoes-orion. It connects Echoes to Orion schemas, dependency injection, async logger context, HTTP routes, and Orion error classes:
Import the adapter once, before modules that declare Echoes controllers:
Applications using @orion-js/components do not need this explicit import because Components installs the adapter automatically.
Schema execution is still runtime behavior, not just TypeScript inference. For every handler, Echoes cleans and validates params before calling resolve, then cleans returns before sending the result. The Orion adapter delegates those operations to @orion-js/schema, so existing custom cleaners, defaults, validations, and ValidationError values keep working.

Creating Echo Controllers

An echoes controller is a class decorated with @Echoes() that contains methods decorated with @EchoRequest() or @EchoEvent():

Echo Request Handlers

Use the @EchoRequest() decorator with the createEchoRequest() function to define methods that handle synchronous requests from other services:

Echo Event Handlers

Use the @EchoEvent() decorator with the createEchoEvent() function to define methods that process asynchronous events:

Event Decorator Options

The createEchoEvent() function accepts options similar to createEchoRequest():
Pulse persists each consumerGroup + topic configuration. Increase the listener’s integer configVersion whenever persisted retry or offset settings change:
The highest version wins during a deploy, so an older replica cannot restore an earlier setting. Using the same version with different settings fails fast. Existing callbacks finish normally and new work converges on the winning configuration. Pulse listeners keep queue state, leases, retries, and completed attempts atomically on their delivery documents. Configure maxConcurrency on an event to override events.pulse.subscription.maxConcurrency for that receiver.

Batch event handlers with Pulse

Use @EchoBatchEvent() when one receiver invocation should process multiple events together:
Each array element contains its cleaned params and individual context, including eventId, headers, creation time, and attempt number. batchSize is a maximum; Pulse immediately invokes the handler with whatever is available and never waits to fill the array. One resolveBatch invocation consumes one worker and one maxConcurrency slot. The complete array is one retry and acknowledgement unit. If the handler throws, Pulse retries the whole delivery, so bulk side effects should use each context.eventId as an idempotency key. Batch event receivers are Pulse-only and require a transaction-capable MongoDB deployment. Kafka consumption fails at startup when a batch receiver is configured. For a hot rollout, first deploy the new handler and packages with batchSize: 1. After every replica understands batch deliveries, increase configVersion and set the intended size. Processes using a normal handler can consume a multi-event delivery sequentially, while batch handlers naturally receive an older one-event delivery as an array of length one.

Making Requests

To make a request to another service:

Publishing Events

To publish an event for other services to consume:

Starting the Echoes Service

To enable echoes in your application, you need to configure and start the echoes service:

Using Echoes without Orion

A standalone service can use a SimpleSchema-compatible object directly. Echoes recognizes objects with clean() and validate() methods and executes both at runtime. typedEchoesSchema() adds the value type when the schema library does not expose it in a form TypeScript can infer:
For synchronous Echoes requests, connect the framework-neutral receiver to the HTTP server of your choice:
Kafka and Pulse are optional transports and are loaded only when selected. Install kafkajs for Kafka, or @orion-js/pulse with its MongoDB peer dependency for Pulse. A service that only uses synchronous requests loads neither event transport.

Incremental migration from Kafka to Pulse

Echoes can listen through Kafka and Pulse at the same time while publishing through exactly one transport. This allows the migration to happen in three independent deployments, without dual-publishing an event. Install Pulse and its MongoDB peer dependency in the application:

1. Prepare every listener

Keep the existing top-level Kafka configuration and add Pulse under events. Deploy this version to every instance before changing any publisher:
At this point, handlers accept events from both systems, but publish() still sends only to Kafka. The event handler API does not change. The Pulse transport uses a MongoDB application pool of at most one connection per service instance by default. Callbacks remain concurrent because they do not retain the pool socket. Set events.pulse.maxPoolSize only when a specific high-throughput service has measured pool contention. events.pulse.maxIdleTimeMS defaults to 30 seconds and lets larger pools contract after bursts. events.pulse.discoveryLockTimeoutMs independently tunes discovery-leader failover; handler locks continue using events.pulse.lockTimeoutMs.
Pulse no longer supports events.pulse.changeStreams. Event discovery always uses polling. Remove the legacy field before deploying this configuration; Echoes fails during startup if the field is still present, including when its value is "disabled".

2. Switch publishers

Once every listener is running the dual-consumer configuration, change only publishTo:
This can be rolled out gradually. Old application instances continue publishing to Kafka and new instances publish to Pulse; the listener fleet consumes both. For a controlled switch, publishTo can also come from an environment flag:

3. Remove Kafka consumption

After every publisher uses Pulse and Kafka has no pending events for the service, stop consuming from Kafka:
The legacy client, consumer, and producer options can then be removed from that application. If only Pulse is configured, Echoes defaults both consumption and publishing to Pulse.
Echoes never publishes the same call to both transports. During a rolling publisher deployment, Kafka and Pulse form two independent delivery streams, so they do not provide a shared global sequence. Use a coordinated publisher cutover after draining Kafka when cross-transport ordering matters.
Pulse is an optional peer dependency and is loaded only when configured. Applications that stay on Kafka do not need to install or initialize it.

Error Handling

Echoes automatically handles errors in request and event handlers:

Custom Error Types

Orionjs handles special error types appropriately:
  • UserError: For expected application errors
  • ValidationError: For data validation errors
Those errors will be automatically propagated back to the requester.

Type Safety

Using TypeScript with schema inference, you can ensure type safety for your echo handlers:

Best Practices

  1. Organize by Domain: Group related echo handlers in the same controller class.
  2. Leverage Dependency Injection: Use @Inject(() => Service) to access repositories and services.
  3. Keep Methods Focused: Each echo handler should have a clear, single responsibility.
  4. Use Strong Typing: Define parameter and return types with schemas and infer TypeScript types.
  5. Handle Errors Gracefully: Catch and properly categorize errors.
  6. Idempotent Handlers: Design event handlers to be idempotent (safe to process the same event multiple times).
  7. Timeout Configuration: Set appropriate timeouts for requests based on expected execution time.
  8. Security: Use the shared key to secure inter-service communication.
  9. Service Discovery: Keep the service registry updated when adding new services.
  10. Monitoring: Implement proper logging and monitoring for echo handlers.