GitHub - btravers/amqp-contract: End-to-end type safety and automatic validation for AMQP messaging

2 min read Original article ↗

Why amqp-contract?

Define your AMQP contracts once — get type safety, autocompletion, and runtime validation everywhere.

  • 🔒 End-to-end type safety — TypeScript knows your message shapes
  • 🔄 Reliable retry — Built-in exponential backoff with Dead Letter Queue support
  • 📄 AsyncAPI compatible — Generate documentation from your contracts

Quick Example

import {
  defineContract,
  defineExchange,
  defineQueue,
  definePublisherFirst,
  defineMessage,
} from "@amqp-contract/contract";
import { TypedAmqpClient } from "@amqp-contract/client";
import { TypedAmqpWorker } from "@amqp-contract/worker";
import { z } from "zod";

// 1. Define resources with Dead Letter Exchange for retry support
const ordersExchange = defineExchange("orders", "topic", { durable: true });
const ordersDlx = defineExchange("orders-dlx", "topic", { durable: true });
const orderProcessingQueue = defineQueue("order-processing", {
  durable: true,
  deadLetter: { exchange: ordersDlx, routingKey: "order.failed" },
});

// 2. Define message with schema validation
const orderMessage = defineMessage(
  z.object({
    orderId: z.string(),
    amount: z.number(),
  }),
);

// 3. Publisher-first pattern ensures consistency
const { publisher: orderCreatedPublisher, createConsumer: createOrderCreatedConsumer } =
  definePublisherFirst(ordersExchange, orderMessage, { routingKey: "order.created" });

// 4. Create consumer from event
const { consumer: processOrderConsumer, binding: orderBinding } =
  createOrderCreatedConsumer(orderProcessingQueue);

// 5. Define contract
const contract = defineContract({
  exchanges: { orders: ordersExchange, ordersDlx },
  queues: { orderProcessing: orderProcessingQueue },
  bindings: { orderBinding },
  publishers: { orderCreated: orderCreatedPublisher },
  consumers: { processOrder: processOrderConsumer },
});

// 6. Type-safe publishing with validation
const client = await TypedAmqpClient.create({
  contract,
  urls: ["amqp://localhost"],
}).resultToPromise();

await client.publish("orderCreated", {
  orderId: "ORD-123", // ✅ TypeScript knows!
  amount: 99.99,
});

// 7. Type-safe consuming with reliable retry
const worker = await TypedAmqpWorker.create({
  contract,
  handlers: {
    processOrder: async (message) => {
      console.log(message.orderId); // ✅ TypeScript knows!
    },
  },
  urls: ["amqp://localhost"],
  retry: { maxRetries: 3, initialDelayMs: 1000 },
}).resultToPromise();

Installation

pnpm add @amqp-contract/contract @amqp-contract/client @amqp-contract/worker

Documentation

📖 Full Documentation →

Packages

Package Description
@amqp-contract/contract Contract builder and type definitions
@amqp-contract/client Type-safe client for publishing
@amqp-contract/worker Type-safe worker with retry support
@amqp-contract/client-nestjs NestJS client integration
@amqp-contract/worker-nestjs NestJS worker integration
@amqp-contract/asyncapi AsyncAPI 3.0 generator

Contributing

See CONTRIBUTING.md.

License

MIT