JavaScript / TypeScript SDK
Install messagex-client and use connections, queues, exchanges, bindings, publishing, consumers, ACK, NACK, and cancellation.
The official MessageX client is published on npm as messagex-client. It is a Node.js TCP client distributed as ECMAScript modules with TypeScript declarations.
Availability: JavaScript / TypeScript is the currently published official SDK. Python and Java clients are roadmap items, not installable official packages yet.
1. Requirements and installation
- Node.js 20 or newer
- A reachable MessageX broker TCP endpoint
- A broker username and password created during deployment
Install the client:
npm install messagex-clientReferences: npm package · SDK source
2. Configure credentials
Keep connection details outside source code. For Node.js 20.6+, create a local .env file:
MESSAGEX_HOST=127.0.0.1
MESSAGEX_PORT=5678
MESSAGEX_USER=admin
MESSAGEX_PASSWORD=replace-with-your-broker-passwordAdd .env to .gitignore. Never commit real credentials.
3. Connect and authenticate
connect() opens a TCP socket and performs the protocol handshake in order:
CONNECTregisters the client name on the connection.AUTHverifies the username and password.- The promise resolves after both commands succeed.
import { MessageX } from 'messagex-client';
const client = new MessageX({
host: process.env.MESSAGEX_HOST ?? '127.0.0.1',
port: Number(process.env.MESSAGEX_PORT ?? 5678),
username: process.env.MESSAGEX_USER,
password: process.env.MESSAGEX_PASSWORD,
clientName: 'orders-service',
});
client.on('disconnect', () => {
console.error('MessageX connection closed');
});
client.on('warning', (error) => {
console.warn('MessageX warning:', error.message);
});
client.on('error', (error) => {
console.error('MessageX socket error:', error.message);
});
await client.connect();
console.log(client.isConnected, client.isAuthenticated);The SDK requires host, username, and password. The default TCP port is 5678. Calling broker methods before connect() completes rejects with a connection error.
4. Understand the broker resources
A producer does not publish directly to a queue. It publishes to an exchange. The exchange evaluates its bindings and routes a copy of the message to each matching queue.
- Exchange: routing entry point for published messages.
- Queue: persisted holding area from which consumers receive messages.
- Binding: relationship between an exchange and a queue, optionally with a routing key and arguments.
- Routing key: string supplied by the publisher and evaluated by the exchange's routing strategy.
- Consumer: active subscription that receives deliveries from a queue.
- Delivery tag: identifier for one in-flight delivery. The broker checks both the tag and owning connection when settling it.
5. Declare a queue
const { queue } = await client.queue.declare({
name: 'orders.created',
durable: true,
exclusive: false,
autoDelete: false,
maxDeliveryCount: 5,
deadLetterQueue: 'orders.dead',
});
console.log(queue);| Option | Meaning |
|---|---|
name | Required queue name; maximum 255 characters |
durable | Persist queue declaration in PostgreSQL |
exclusive | Restrict access to the declaring connection |
autoDelete | Queue declaration option stored with the queue |
maxDeliveryCount | Positive delivery limit used by retry/dead-letter handling |
deadLetterQueue | Destination queue name when the delivery limit is reached |
The broker associates an exclusive queue with its declaring connection. Other connections cannot consume from, bind to, or delete that exclusive queue.
6. Declare an exchange
const { exchange } = await client.exchange.declare({
name: 'orders.events',
type: 'topic',
durable: true,
});
console.log(exchange);MessageX supports four exchange types:
| Type | Routing behavior | Example |
|---|---|---|
direct | Exact routing-key match | orders.created matches orders.created |
fanout | Routes to every queue bound to the exchange | Routing key is not used for selection |
topic | Dot-separated pattern matching; * matches one word and # matches zero or more words | orders.* matches orders.created |
headers | Compares message headers with binding arguments | { region: 'apac' } |
Exchange names are required and limited to 255 characters. The durable option defaults according to the broker's declaration defaults.
7. Bind a queue to an exchange
A binding tells the exchange which queue should receive matching messages.
Direct binding
await client.exchange.bind({
exchangeName: 'orders.events',
queueName: 'orders.created',
routingKey: 'orders.created',
});Topic binding
await client.exchange.bind({
exchangeName: 'orders.events',
queueName: 'all.order.events',
routingKey: 'orders.*',
});Fanout binding
await client.exchange.bind({
exchangeName: 'broadcast.events',
queueName: 'notifications.email',
});Headers binding
await client.exchange.bind({
exchangeName: 'events.by-header',
queueName: 'apac.events',
arguments: {
'x-match': 'all',
region: 'apac',
priority: 'high',
},
});For headers exchanges, x-match: 'all' requires every supplied binding argument to match; x-match: 'any' accepts a match on any supplied argument. Binding arguments are limited to 32 entries.
Remove a binding with the same exchange, queue, routing key, and arguments:
await client.exchange.unbind({
exchangeName: 'orders.events',
queueName: 'orders.created',
routingKey: 'orders.created',
});8. Publish a message
const result = await client.publish({
exchangeName: 'orders.events',
routingKey: 'orders.created',
message: {
orderId: 'ORD-1001',
amount: 2500,
status: 'created',
},
headers: {
source: 'checkout-service',
region: 'apac',
},
});
console.log('Queues matched:', result.routedQueues);The message body must be JSON-serializable. The broker commits routed message records to PostgreSQL before it asks the consumer manager to dispatch available messages. routedQueues reports the number of matched destination queues, not the number of consumers.
If no binding matches, the publish can succeed with zero routed queues. Confirm exchange and binding configuration when messages are not arriving.
9. Consume messages
A consumer registers against an existing queue. The broker returns a consumerTag and begins delivering available messages through the callback.
const subscription = await client.consume({
queueName: 'orders.created',
onMessage: async (message) => {
console.log('Body:', message.body);
console.log('Headers:', message.headers);
console.log('Delivery count:', message.deliveryCount);
await processOrder(message.body);
await message.ack();
},
});
console.log('Consumer tag:', subscription.consumerTag);The current broker uses prefetch count 1 per connection and queue. The consumer manager keeps active subscriptions in memory; the subscription itself is not a durable database resource.
10. ACK: acknowledge successful processing
Call message.ack() after your application has completed the work it considers successful:
await processOrder(message.body);
await message.ack();The broker verifies that the delivery tag belongs to the current connection. A valid ACK removes the message record and releases the consumer's prefetch slot so another message can be dispatched.
Do not ACK before completing important side effects. MessageX does not provide a distributed transaction between the broker and your application database.
11. NACK: reject a delivery
Use message.nack() when processing fails:
try {
await processOrder(message.body);
await message.ack();
} catch (error) {
console.error(error);
await message.nack({ requeue: true });
}| Call | Current behavior |
|---|---|
nack({ requeue: true }) | Return the message to the ready state while under the configured delivery limit |
nack({ requeue: true }) at the delivery limit | Move to the configured dead-letter queue, or mark dead if no DLQ exists |
nack({ requeue: false }) | Delete/discard the message record |
A failed message can be delivered more than once. Make consumer handlers idempotent when processing external side effects.
12. Cancel a consumer
await subscription.cancel();Cancellation stops that consumer subscription. An already delivered in-flight message can still be settled using its ACK/NACK methods.
13. Delete queues and exchanges
These operations are destructive. Deleting a queue also removes its associated message and binding records according to the database relationships.
await client.queue.delete('orders.created');
await client.exchange.delete('orders.events');Delete only after confirming that no producer or consumer still depends on the resource.
14. Close the connection cleanly
await subscription.cancel();
await client.close();For a process that manages multiple subscriptions, cancel each subscription before closing the client. On connection loss, the broker releases unsettled deliveries owned by that connection; the SDK does not automatically reconnect or restore subscriptions.
15. Client options
| Option | Type | Default | Description |
|---|---|---|---|
host | string | Required | Broker hostname or IP |
port | number | 5678 | Broker TCP port |
username | string | Required | Broker username |
password | string | Required | Broker password |
clientName | string | messagex-node-client | Connection label |
connectTimeout | number | 10000 | Socket connection timeout in milliseconds |
requestTimeout | number | 15000 | Command response timeout in milliseconds |
maxFrameSize | number | 1048576 | Maximum frame payload size in bytes |
16. SDK events and errors
The SDK extends Node.js EventEmitter.
connect,disconnect,close— connection lifecycle.message— each delivered message.event— non-message broker events.warning— recoverable SDK/protocol warning.error— socket or protocol error; attach a listener to handle it.consumerError— an application message callback threw or rejected.
Exported error classes:
MessageXErrorMessageXConnectionErrorMessageXTimeoutErrorMessageXBrokerError
17. Current limitations
The current SDK does not implement automatic reconnect, subscription restoration, browser/WebSocket transport, TLS, transactions, batch publishing, or management HTTP API methods. The broker's TCP protocol is not encrypted; use a private network, VPN, SSH tunnel, or other protected transport for remote deployments.