Skip to content

Handling messages ​

This page declares a command, writes a handler for it, registers the handler with the bus and sends the command. It uses a hotel booking application, where a ReserveRoom command reserves a room and publishes RoomReserved once it's done.

  1. Declare the messages

    A command is a class that extends Command, with a static NAME that the bus routes it by. Events extend Event the same way.

    ts
    import { Command, Event } from '@node-ts/bus-messages'
    
    export class ReserveRoom extends Command {
      static NAME = 'reservations/reserve-room'
      $name = ReserveRoom.NAME
      $version = 0
    
      constructor(
        readonly roomId: string,
        readonly bookingId: string
      ) {
        super()
      }
    }
    
    export class RoomReserved extends Event {
      static NAME = 'reservations/room-reserved'
      $name = RoomReserved.NAME
      $version = 0
    
      constructor(
        readonly roomId: string,
        readonly bookingId: string
      ) {
        super()
      }
    }
  2. Write a handler

    handlerFor declares a function that handles one type of message. It's called with the message, its attributes and a HandlerContext, which sends and publishes through the bus that received the message.

    ts
    import { handlerFor } from '@node-ts/bus-core'
    import { ReserveRoom, RoomReserved } from '../messages'
    import { reservationService } from '../services'
    
    export const reserveRoomHandler = handlerFor(
      ReserveRoom,
      async (command, _attributes, ctx) => {
        await reservationService.reserveRoom(command.roomId, command.bookingId)
        // Published once the handler resolves, and dropped if it throws
        await ctx.publish(new RoomReserved(command.roomId, command.bookingId))
      }
    )

    TIP

    Keep handlers thin and delegate the work to your own services. That keeps the messaging concerns of your application apart from the work it does.

  3. Register the handler and send the command

    ts
    import { Bus } from '@node-ts/bus-core'
    import { reserveRoomHandler } from './handlers/reserve-room-handler'
    import { messageTypes } from './message-types.generated'
    import { ReserveRoom } from './messages'
    
    const bus = Bus.configure()
      .withMessageTypes(messageTypes)
      .withHandler(reserveRoomHandler)
      .build()
    
    await bus.initialize()
    // Start the bus to begin handling messages
    await bus.start()
    
    await bus.send(
      new ReserveRoom(
        '63a65cf0-d239-4b83-96da-f33f013db23a',
        '12b85a56-e929-47a8-9ac3-e87739d5d215'
      )
    )

When the handler resolves, the message is deleted from the queue, and RoomReserved is published. If it throws, RoomReserved is dropped and the message goes back on the queue to be retried, as described in Retry strategies.

A message is sent to the transport's queue, read by the bus and dispatched to its handlers. A handled message is deleted. A failed one is returned to the queue after a delay, and goes to the dead letter queue once it's out of attempts.
What happens to a message

Testing a handler ​

A handler is a plain function, and HandlerContext is an interface, so a test can call the handler with a fake context. messageAttributes() from @node-ts/bus-messages fills in empty attributes.

ts
import { HandlerContext } from '@node-ts/bus-core'
import { Event, messageAttributes } from '@node-ts/bus-messages'
import { deepStrictEqual } from 'node:assert'
import { reserveRoomHandler } from './handlers/reserve-room-handler'
import { ReserveRoom, RoomReserved } from './messages'

// In a test, with any test runner
const published: Event[] = []
const ctx: HandlerContext = {
  correlationId: 'test',
  send: async () => {},
  publish: async event => {
    published.push(event)
  },
  failMessage: async () => {},
  returnMessage: async () => {}
}

await reserveRoomHandler.messageHandler(
  new ReserveRoom('room-1', 'booking-1'),
  // Empty attributes and sticky attributes
  messageAttributes(),
  ctx
)

deepStrictEqual(published, [new RoomReserved('room-1', 'booking-1')])

See also ​

Released under the MIT License.