Skip to main content

Quick Start Guide

Get started with NatsPubsub in under 10 minutes. This guide covers the basics for both JavaScript/TypeScript and Ruby implementations.

What is NatsPubsub?

NatsPubsub is a production-ready pub/sub messaging library built on NATS JetStream. It provides:

  • Declarative API: Class-based subscribers for clean, maintainable code
  • Built-in Reliability: Automatic Inbox/Outbox patterns and Dead Letter Queue
  • Cross-Language: Identical APIs in JavaScript and Ruby
  • Testing Support: Fake and inline modes for easy testing
  • Auto-Topology: Automatic stream and consumer management

Prerequisites

For JavaScript/TypeScript

  • Node.js >= 20.x
  • TypeScript (optional, recommended)

For Ruby

  • Ruby >= 2.7
  • Rails (optional)

For Both

  • NATS Server with JetStream running locally or remotely

Don't have NATS installed? Start it with Docker:

docker run -d -p 4222:4222 nats:latest -js

Choose Your Language

NatsPubsub provides native implementations for both JavaScript and Ruby with identical concepts and patterns.

JavaScript/TypeScript

Perfect for Node.js applications, Next.js, Express, NestJS, and modern JavaScript projects.

Installation:

npm install nats-pubsub

Quick Example:

import NatsPubsub, { Subscriber, TopicMetadata } from "nats-pubsub";

// Configure
NatsPubsub.configure({
natsUrls: "nats://localhost:4222",
env: "development",
appName: "my-app",
});

// Publish
await NatsPubsub.publish("user.created", {
userId: "123",
email: "user@example.com",
});

// Subscribe
class UserCreatedSubscriber extends Subscriber<
Record<string, unknown>,
TopicMetadata
> {
constructor() {
super("development.my-app.user.created");
}

async handle(
message: Record<string, unknown>,
metadata: TopicMetadata,
): Promise<void> {
console.log("User created:", message);
}
}

NatsPubsub.registerSubscriber(new UserCreatedSubscriber());
await NatsPubsub.start();

Learn More: JavaScript/TypeScript Quick Start

Ruby

Perfect for Rails applications, Sinatra, Ruby microservices, and Ruby projects.

Installation:

gem install nats_pubsub
# or add to Gemfile
gem 'nats_pubsub'

Quick Example:

require 'nats_pubsub'

# Configure
NatsPubsub.configure do |config|
config.servers = 'nats://localhost:4222'
config.env = 'development'
config.app_name = 'my-app'
end

# Publish
NatsPubsub.publish('user.created', {
user_id: '123',
email: 'user@example.com'
})

# Subscribe
class UserCreatedSubscriber < NatsPubsub::Subscriber
subscribe_to 'user.created'

def handle(message, context)
puts "User created: #{message}"
end
end

# Start subscribers
NatsPubsub::Manager.start

Learn More: Ruby Quick Start

Core Concepts

1. Topics and Subjects

NatsPubsub uses topics (application-level) which are automatically converted to subjects (NATS-level):

Topic:   user.created
Subject: development.my-app.user.created
└────┬────┘ └───┬───┘ └────┬─────┘
env appName topic

Wildcards:

  • * matches one level: user.* matches user.created, user.updated
  • > matches multiple levels: user.> matches user.created, user.profile.updated

2. Publishers

Publishers send messages to topics:

JavaScript:

await NatsPubsub.publish("order.created", {
orderId: "123",
total: 99.99,
});

Ruby:

NatsPubsub.publish('order.created', {
order_id: '123',
total: 99.99
})

3. Subscribers

Subscribers receive and process messages:

JavaScript:

class OrderSubscriber extends Subscriber<OrderData, TopicMetadata> {
constructor() {
super("*.*.order.created");
}

async handle(message: OrderData, metadata: TopicMetadata): Promise<void> {
await processOrder(message);
}
}

Ruby:

class OrderSubscriber < NatsPubsub::Subscriber
subscribe_to 'order.created'

def handle(message, context)
process_order(message)
end
end

4. Message Flow

Your First Pub/Sub System

Let's build a simple user registration system that demonstrates the core concepts.

1. Start NATS Server

docker run -d -p 4222:4222 nats:latest -js

2. Create Project Structure

JavaScript:

mkdir my-pubsub-app && cd my-pubsub-app
npm init -y
npm install nats-pubsub
npm install --save-dev tsx typescript @types/node

Ruby:

mkdir my-pubsub-app && cd my-pubsub-app
bundle init
# Add to Gemfile: gem 'nats_pubsub'
bundle install

3. Implement Publisher

JavaScript (publisher.ts):

import NatsPubsub from "nats-pubsub";

NatsPubsub.configure({
natsUrls: "nats://localhost:4222",
env: "development",
appName: "user-service",
});

async function registerUser(email: string, name: string) {
const userId = Date.now().toString();

await NatsPubsub.publish("user.registered", {
userId,
email,
name,
registeredAt: new Date().toISOString(),
});

console.log(`✓ User registered: ${email}`);
}

registerUser("john@example.com", "John Doe").catch(console.error);

Ruby (publisher.rb):

require 'nats_pubsub'

NatsPubsub.configure do |config|
config.servers = 'nats://localhost:4222'
config.env = 'development'
config.app_name = 'user-service'
end

def register_user(email, name)
user_id = Time.now.to_i.to_s

NatsPubsub.publish('user.registered', {
user_id: user_id,
email: email,
name: name,
registered_at: Time.now.iso8601
})

puts "✓ User registered: #{email}"
end

NatsPubsub::Connection.connect
register_user('john@example.com', 'John Doe')
NatsPubsub::Connection.close

4. Implement Subscribers

JavaScript (subscribers.ts):

import NatsPubsub, { Subscriber, TopicMetadata } from "nats-pubsub";

// Email notification subscriber
class EmailNotificationSubscriber extends Subscriber<
Record<string, unknown>,
TopicMetadata
> {
constructor() {
super("development.user-service.user.registered");
}

async handle(
message: Record<string, unknown>,
metadata: TopicMetadata,
): Promise<void> {
console.log(`📧 Sending welcome email to ${message.email}`);
// Simulate sending email
await new Promise((resolve) => setTimeout(resolve, 100));
console.log(`✓ Welcome email sent to ${message.email}`);
}
}

// Analytics subscriber
class AnalyticsSubscriber extends Subscriber<
Record<string, unknown>,
TopicMetadata
> {
constructor() {
super("development.user-service.user.registered");
}

async handle(
message: Record<string, unknown>,
metadata: TopicMetadata,
): Promise<void> {
console.log(`📊 Recording analytics for user ${message.userId}`);
// Simulate analytics
await new Promise((resolve) => setTimeout(resolve, 50));
console.log(`✓ Analytics recorded for user ${message.userId}`);
}
}

// Configure and start
NatsPubsub.configure({
natsUrls: "nats://localhost:4222",
env: "development",
appName: "user-service",
});

NatsPubsub.registerSubscriber(new EmailNotificationSubscriber());
NatsPubsub.registerSubscriber(new AnalyticsSubscriber());

await NatsPubsub.start();
console.log("✓ Subscribers started, waiting for messages...");

process.on("SIGINT", async () => {
console.log("\nShutting down...");
await NatsPubsub.stop();
process.exit(0);
});

Ruby (subscribers.rb):

require 'nats_pubsub'

# Email notification subscriber
class EmailNotificationSubscriber < NatsPubsub::Subscriber
subscribe_to 'user.registered'

def handle(message, context)
puts "📧 Sending welcome email to #{message['email']}"
sleep 0.1 # Simulate sending email
puts "✓ Welcome email sent to #{message['email']}"
end
end

# Analytics subscriber
class AnalyticsSubscriber < NatsPubsub::Subscriber
subscribe_to 'user.registered'

def handle(message, context)
puts "📊 Recording analytics for user #{message['user_id']}"
sleep 0.05 # Simulate analytics
puts "✓ Analytics recorded for user #{message['user_id']}"
end
end

# Configure and start
NatsPubsub.configure do |config|
config.servers = 'nats://localhost:4222'
config.env = 'development'
config.app_name = 'user-service'
end

NatsPubsub::Manager.start
puts '✓ Subscribers started, waiting for messages...'

Signal.trap('INT') do
puts "\nShutting down..."
NatsPubsub::Manager.stop
exit
end

sleep

5. Run the System

Terminal 1 - Start Subscribers:

# JavaScript
npx tsx subscribers.ts

# Ruby
ruby subscribers.rb

Terminal 2 - Publish Messages:

# JavaScript
npx tsx publisher.ts

# Ruby
ruby publisher.rb

You should see both subscribers process the message independently!

What's Happening?

  1. Publisher sends a user.registered event to NATS
  2. NATS routes the message to all matching subscribers
  3. Email Subscriber sends a welcome email
  4. Analytics Subscriber records user registration
  5. Both run independently and asynchronously

Key Benefits Demonstrated

  1. Decoupling: Publisher doesn't know about subscribers
  2. Fan-out: One message, multiple processors
  3. Reliability: NATS ensures message delivery
  4. Scalability: Add more subscribers without changing publisher
  5. Cross-Language: JavaScript and Ruby services work together

Next Steps by Use Case

I'm Building a Web Application

JavaScript/TypeScript:

Ruby:

I Want Guaranteed Delivery

Learn about reliability patterns:

I Need to Handle Failures

Learn about error handling:

I'm Ready for Production

Follow deployment guides:

I Want to Test My Code

Learn testing approaches:

Language-Specific Quick Starts

For detailed, language-specific guides with more examples:

Common Questions

Do I need both JavaScript and Ruby?

No! Choose one based on your tech stack. They're completely independent and can interoperate.

Can JavaScript and Ruby services communicate?

Yes! Messages published from JavaScript can be consumed by Ruby and vice versa. NatsPubsub handles serialization automatically.

How is this different from HTTP APIs?

  • Async: Fire and forget, no waiting for response
  • Decoupled: Services don't need to know about each other
  • Scalable: Horizontal scaling without code changes
  • Reliable: Built-in retry, DLQ, and transactional patterns

Is this production-ready?

Yes! NatsPubsub is battle-tested with:

  • Built-in reliability patterns
  • Comprehensive error handling
  • Monitoring and observability
  • Performance optimizations

Troubleshooting

NATS Connection Failed

Error: Could not connect to NATS server

Solution:

# Check NATS is running
docker ps | grep nats

# Start NATS if needed
docker run -d -p 4222:4222 nats:latest -js

Messages Not Being Received

  1. Check subscriber is running before publishing
  2. Verify subject matches - enable debug logging:

JavaScript:

NatsPubsub.configure({
// ... other config
logger: {
debug: console.debug,
info: console.info,
error: console.error,
},
});

Ruby:

NatsPubsub.configure do |config|
config.log_level = :debug
end

Module Not Found

JavaScript:

npm install nats-pubsub

Ruby:

gem install nats_pubsub
# or
bundle install

Additional Resources


Ready to dive deeper?