fastify-rabbitmq
Version:
A Fastify RabbitMQ Plugin Developed in Pure TypeScript.
292 lines (224 loc) • 9.55 kB
Markdown
# 🐰 Fastify RabbitMQ
A Fastify RabbitMQ plugin developed in pure TypeScript.
It wraps the [`rabbitmq-client`](https://www.npmjs.com/package/rabbitmq-client) package so a Fastify
app can publish, consume, and RPC over RabbitMQ (AMQP 0-9-1).
The build exports valid ESM and CJS for cross-compatibility.
If you use this package, please consider giving it a ⭐ — it raises visibility and brings in more
contribution from the outside.
> This documentation covers **how to use the plugin**. The decorator `app.rabbitmq` is a live
> `rabbitmq-client` `Connection`, so its full API is available — for publisher/consumer/RPC option
> details, see [External Libraries](#-external-libraries).
> 🟢 **Requires Node.js ≥ 20.15.**
## Table of Contents
1. [Install](#-install)
2. [Basic Usage](#-basic-usage)
3. [Recipes](#-recipes)
1. [Publish a message](#1-publish-a-message)
2. [Consume a queue](#2-consume-a-queue)
3. [RPC (request/response)](#3-rpc-requestresponse)
4. [Declare exchanges, queues, and bindings](#4-declare-exchanges-queues-and-bindings)
5. [Multiple connections with namespaces](#5-multiple-connections-with-namespaces)
6. [Encapsulate messaging in your own plugin](#6-encapsulate-messaging-in-your-own-plugin)
4. [API Reference](#-api-reference-fastifyrabbitmq)
5. [Plugin Options](#-plugin-options)
6. [External Libraries](#-external-libraries)
7. [Acknowledgements](#-acknowledgements)
8. [License](#-license)
## 📦 Install
```shell
npm install fastify-rabbitmq
```
Requires Node.js ≥ 20.15.
## 🚀 Basic Usage
Register the plugin (before any routes that use it):
```ts
import fastify from "fastify";
import fastifyRabbitMQ from "fastify-rabbitmq";
const app = fastify();
await app.register(fastifyRabbitMQ, {
connection: "amqp://guest:guest@localhost",
});
```
Registering decorates the Fastify instance with `app.rabbitmq` — a live `rabbitmq-client`
`Connection`. Use it to create publishers, consumers, and RPC clients, or to declare topology.
## 🧩 Recipes
### 1. Publish a message
```ts
const publisher = app.rabbitmq.createPublisher({
confirm: true,
maxAttempts: 1,
});
await publisher.send("foo", "bar"); // routing key "foo", body "bar"
```
In a route, reach the instance via `request.server`:
```ts
app.get("/publish", async (request, reply) => {
const publisher = request.server.rabbitmq.createPublisher({ confirm: true });
await publisher.send("foo", "bar");
return { sent: true };
});
```
### 2. Consume a queue
```ts
const consumer = app.rabbitmq.createConsumer(
{
queue: "foo",
queueOptions: { durable: true },
},
async (msg) => {
app.log.info({ body: msg.body }, "received");
// ...handle the message...
},
);
```
### 3. RPC (request/response)
```ts
const rpc = app.rabbitmq.createRPCClient({ confirm: true });
const response = await rpc.send("my-rpc-queue", "ping");
app.log.info({ response: response.body }, "rpc reply");
```
### 4. Declare exchanges, queues, and bindings
```ts
await app.rabbitmq.queueDeclare({ queue: "foo", durable: true });
await app.rabbitmq.exchangeDeclare({ exchange: "my-exchange", type: "topic" });
await app.rabbitmq.queueBind({
queue: "foo",
exchange: "my-exchange",
routingKey: "foo.#",
});
```
### 5. Multiple connections with namespaces
To talk to more than one broker (or keep separate connections), register the plugin once per
`namespace`; each is reachable at `app.rabbitmq.<namespace>`:
```ts
await app.register(fastifyRabbitMQ, {
connection: "amqp://guest:guest@broker-a",
namespace: "a",
});
await app.register(fastifyRabbitMQ, {
connection: "amqp://guest:guest@broker-b",
namespace: "b",
});
const pubA = app.rabbitmq.a.createPublisher();
const pubB = app.rabbitmq.b.createPublisher();
```
Registering the same namespace twice throws `FASTIFY_RABBIT_MQ_ERR_SETUP_ERRORS`.
### 6. Encapsulate messaging in your own plugin
This is the pattern the plugin is built for, and the reason it is a plugin at all.
Fastify's encapsulation lets you keep every messaging concern — the connection, the
topology, the publishers, the consumers, and their shutdown — in **one plugin**, and
expose just a small, intent-named surface (a decorator like `app.events`) to the rest
of the app. Your routes publish business events; they never see exchanges, routing
keys, or the AMQP client.
Wrap it with [`fastify-plugin`](https://github.com/fastify/fastify-plugin) so the
decorator is visible to sibling plugins and routes (without `fp`, the decorator would
be trapped inside this plugin's encapsulation context):
```ts
// plugins/messaging.ts
import fp from "fastify-plugin";
import fastifyRabbitMQ, { type Publisher } from "fastify-rabbitmq";
declare module "fastify" {
interface FastifyInstance {
events: Publisher;
}
}
export default fp(
async (app) => {
// 1. Open the connection.
await app.register(fastifyRabbitMQ, {
connection: process.env.RABBITMQ_URL ?? "amqp://guest:guest@localhost",
});
// 2. Declare the topology and a publisher once, at startup. The publisher
// re-declares these on every reconnect, so the exchange always exists
// before the first send.
const publisher = app.rabbitmq.createPublisher({
confirm: true,
maxAttempts: 2,
exchanges: [{ exchange: "events", type: "topic" }],
});
// 3. Expose one intent-named sender. Routes call app.events.send(...) and
// stay ignorant of AMQP.
app.decorate("events", publisher);
// 4. Run consumers as part of the app lifecycle. The consumer manages its
// own channel and re-subscribes across reconnects.
const consumer = app.rabbitmq.createConsumer(
{
queue: "user-events",
queueOptions: { durable: true },
queueBindings: [{ exchange: "events", routingKey: "user.#" }],
},
async (msg) => {
app.log.info({ body: msg.body }, "user event");
// ...handle the message; throw to nack/retry...
},
);
// 5. Tear everything down with the app, so a restart or test closes
// cleanly instead of leaking connections.
app.addHook("onClose", async () => {
await consumer.close();
await publisher.close();
});
},
{ name: "messaging" },
);
```
Register it once, then publish from anywhere:
```ts
import messaging from "./plugins/messaging";
await app.register(messaging);
app.post("/signup", async (request, reply) => {
await app.events.send(
{ exchange: "events", routingKey: "user.created" },
request.body,
);
return { accepted: true };
});
```
Why this shape works well:
- **One place owns messaging.** Connection, topology, publishers, consumers, and
shutdown live together; the rest of the app depends only on `app.events`.
- **Startup declares, routes send.** Topology is declared once at boot, so the first
request never races a missing exchange.
- **Lifecycle is handled.** The `onClose` hook closes the consumer and publisher with
the app — important for graceful shutdown and for tests that start and stop Fastify
repeatedly.
- **Swappable.** Because routes only know `app.events`, you can change brokers, add a
[namespace](#5-multiple-connections-with-namespaces), or stub the decorator in a test
without touching route code.
> The types you need — `Publisher`, `Consumer`, `RPCClient`, `Connection`, `ConnectionOptions`,
> `Envelope`, the message types, and the AMQP error classes — are re-exported from
> `fastify-rabbitmq`. Import them from here, not from `rabbitmq-client`.
## 📖 API Reference (`fastify.rabbitmq`)
`app.rabbitmq` is a `rabbitmq-client` `Connection` (and, with namespaces, an index of them). The
methods you will use most:
| Method | Description |
|---|---|
| `createPublisher(options?)` | Create a `Publisher` (`.send(routingKey, body)`). |
| `createConsumer(options, handler)` | Create a `Consumer` bound to a queue; `handler(msg)` runs per message. |
| `createRPCClient(options?)` | Create an RPC client (`.send(queue, body)` returns the reply). |
| `exchangeDeclare(params)` | Declare an exchange. |
| `queueDeclare(params)` | Declare a queue. |
| `queueBind(params)` | Bind a queue to an exchange. |
| `acquire()` | Acquire a raw channel. |
| `ready()` | Resolve once the connection is established. |
| `close()` | Close the connection. |
The full option shapes for each come from `rabbitmq-client` — see [External Libraries](#-external-libraries).
## ⚙️ Plugin Options
Pass these to `app.register(fastifyRabbitMQ, options)`:
### `connection`
A connection string (e.g. `"amqp://guest:guest@localhost"`) or a `ConnectionOptions` object from
`rabbitmq-client` pointing at the broker. **Required.**
### `namespace`
`string` — register the plugin more than once by giving each instance a unique namespace; the
connection is then exposed at `app.rabbitmq.<namespace>`. Re-using a namespace fails to load.
## 🔌 External Libraries
This plugin documents only its own surface. For publisher/consumer/RPC options, connection options
(`ConnectionOptions`), TLS, and reconnect behavior, see the package it wraps:
- [`rabbitmq-client`](https://www.npmjs.com/package/rabbitmq-client) — the underlying AMQP client
(repo: [cody-greene/node-rabbitmq-client](https://github.com/cody-greene/node-rabbitmq-client)).
## 🙏 Acknowledgements
- [`rabbitmq-client`](https://www.npmjs.com/package/rabbitmq-client)
- [Fastify](https://fastify.dev/)
- ...and my Wife and Baby Girl.
## 📄 License
Licensed under [MIT](./LICENSE).