---
url: https://node-ts.github.io/bus/getting-started/handling-messages.md
description: Declare a message, handle it with a function, send it, and test the handler.
---

# 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.

<Steps>

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`](/api/bus-core/interfaces/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'
     )
   )

   ```

</Steps>

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](/guide/retry-strategies).

<Diagram src="/diagrams/message-flow.svg" alt="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." caption="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

* [Messages](/guide/messages), for commands, events and messages declared without a class
* [Message attributes](/guide/message-attributes), for the metadata that travels with a message
* [Shutting down cleanly](/getting-started/shutting-down)
