@nats-ms/core
v0.2.0
Published
Type-safe NATS wrapper for microservices with messaging patterns
Maintainers
Readme
@nats-ms/core
⚡ Type-safe NATS wrapper for microservices with automatic type inference and built-in messaging patterns.
Features
- 🎯 Full Type Safety - Automatic type inference from ServiceMap (no manual type annotations needed!)
- 📘 TypeScript-First - Built with TypeScript, for TypeScript
- 🔄 Messaging Patterns - Request-Reply, Pub-Sub, Scatter-Gather, Sharding
- 🛡️ Resilience - Circuit Breaker, Retry strategies, Timeouts
- 🔍 Auto-Discovery - Services announce themselves automatically
- 📊 Built-in Metrics - Prometheus-compatible metrics
- 🎭 Middleware - Request/Response interceptors
- 📝 Schema Validation - Built-in Zod validation
Installation
npm install @nats-ms/core nats zodQuick Start
1. Define ServiceMap
import { ServiceMap } from '@nats-ms/core';
import { z } from 'zod';
export class UserServiceMap extends ServiceMap {
services = {
'user.create': {
request: z.object({
username: z.string(),
email: z.string().email(),
}),
response: z.object({
userId: z.string(),
username: z.string(),
}),
},
'user.get': {
request: z.object({
userId: z.string(),
}),
response: z.object({
userId: z.string(),
username: z.string(),
email: z.string(),
}),
},
} as const;
}2. Create Service
import { NATSInstanceFactory, createLogger } from '@nats-ms/core';
import { UserServiceMap } from './service-map';
const logger = createLogger({ serviceName: 'user-service' });
// Create factory
const factory = NATSInstanceFactory.create(
{
servers: 'nats://localhost:4222',
name: 'user-service',
maxReconnectAttempts: -1,
reconnectTimeWait: 2000,
},
logger
);
await factory.connect();
// Create instance with ServiceMap
const instance = factory.createInstance(new UserServiceMap(), {
discovery: {
version: '1.0.0',
},
});3. Subscribe to Topics
// ✅ Types are inferred automatically from ServiceMap!
instance.subscribe('user.create', async (req) => {
const { username, email } = req.data; // ✅ Fully typed!
const userId = await createUser(username, email);
// ✅ TypeScript validates response type
return {
userId,
username,
};
});4. Request from Another Service
// Make request
const response = await instance.request('user.create', {
username: 'john',
email: '[email protected]',
});
// ✅ Use type guard for type narrowing
if (isSuccessResponse(response)) {
console.log(response.data.userId); // ✅ string (not undefined!)
console.log(response.data.username); // ✅ string
}Type System
Automatic Type Inference
❌ DON'T add explicit type annotations:
// ❌ WRONG - types will be 'any'
instance.subscribe('user.create', async (req: MSRequest) => {
req.data // ❌ type is 'any'
});✅ DO let TypeScript infer types automatically:
// ✅ CORRECT - types inferred from ServiceMap
instance.subscribe('user.create', async (req) => {
req.data.username // ✅ string
req.data.email // ✅ string
});Utility Types
import { InferRequest, InferResponse, InferTopics } from '@nats-ms/core';
type Services = typeof UserServiceMap.prototype.services;
// Extract all topic names
type Topics = InferTopics<Services>; // 'user.create' | 'user.get'
// Extract request type for specific topic
type CreateRequest = InferRequest<Services, 'user.create'>;
// { username: string; email: string; }
// Extract response type
type CreateResponse = InferResponse<Services, 'user.create'>;
// { userId: string; username: string; }Type Guards
import { isSuccessResponse, isErrorResponse } from '@nats-ms/core';
const response = await instance.request('user.create', data);
// Narrow to success type
if (isSuccessResponse(response)) {
response.data.userId // ✅ string (NOT undefined)
}
// Narrow to error type
if (isErrorResponse(response)) {
console.log(response.errorCode); // ✅ number
console.log(response.errorMessage); // ✅ string
}Messaging Patterns
Request-Reply
const response = await instance.request('user.get', { userId: '123' });Pub-Sub (Fire and Forget)
instance.send('user.updated', { userId: '123', username: 'john' });Broadcast
instance.send('notification.alert', { message: 'Alert!' }, { broadcast: true });Scatter-Gather (Request All)
const responses = await instance.requestAll('inventory.check', {
productId: 'p1',
});
// Aggregate results from all instances
const totalStock = responses
.filter(isSuccessResponse)
.reduce((sum, r) => sum + r.data.quantity, 0);First Responder
const response = await instance.requestFirst('cache.get', { key: 'user:123' });Sharding
instance.sendSharded(
'analytics.track',
{ userId: '123', event: 'click' },
'user:123', // shard key
10 // total shards
);Resilience Patterns
Circuit Breaker
const response = await instance.request(
'external-api.call',
{ data },
{
circuitBreaker: {
failureThreshold: 5,
resetTimeout: 30000,
},
}
);Retry Strategy
const response = await instance.request(
'unstable-service.call',
{ data },
{
retry: {
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2,
},
}
);Timeout
const response = await instance.request(
'slow-service.call',
{ data },
{
timeout: 5000, // 5 seconds
}
);Middleware
// Logging middleware
instance.use(async (req, next) => {
console.log(`Received: ${req.topic}`);
const response = await next();
console.log(`Responded: ${response.isSuccess}`);
return response;
});
// Auth middleware
instance.use(async (req, next) => {
const token = req.headers['authorization'];
if (!token) {
return {
isSuccess: false,
errorCode: 401,
errorMessage: 'Unauthorized',
};
}
return next();
});Auto-Discovery
Services automatically announce themselves:
const instance = factory.createInstance(new UserServiceMap(), {
discovery: {
enabled: true, // default: true
version: '1.0.0',
announceInterval: 30000, // 30 seconds
// topics auto-detected from subscriptions
},
});Metrics
Built-in Prometheus-compatible metrics:
const metrics = factory.getMetrics();
// Available metrics:
// - messages_sent_total
// - messages_received_total
// - request_duration_seconds
// - errors_total
// - active_connectionsError Handling
import { MSError } from '@nats-ms/core';
instance.subscribe('user.create', async (req) => {
if (await userExists(req.data.email)) {
throw new MSError(409, 'User already exists');
}
return createUser(req.data);
});Lifecycle Events
import { LifecycleEvent } from '@nats-ms/core';
instance.on(LifecycleEvent.REQUEST_START, (data) => {
console.log(`Request started: ${data.topic}`);
});
instance.on(LifecycleEvent.REQUEST_END, (data) => {
console.log(`Request completed in ${data.duration}ms`);
});
instance.on(LifecycleEvent.REQUEST_ERROR, (data) => {
console.error(`Request error: ${data.error.message}`);
});Graceful Shutdown
process.on('SIGINT', async () => {
await instance.shutdown();
await factory.close();
process.exit(0);
});API Reference
NATSInstanceFactory
create(config, logger?)- Create factory instanceconnect()- Connect to NATS servercreateInstance(serviceMap, options?)- Create typed instancecreateUnmappedInstance<T>(options?)- Create instance without ServiceMapclose()- Close connectiongetMetrics()- Get metrics instance
NATSInstance
subscribe(topic, handler, options?)- Subscribe to topicrequest(topic, data?, options?)- Request-reply patternrequestAll(topic, data?, options?)- Scatter-gather patternrequestFirst(topic, data?, options?)- First responder patternsend(topic, data?, options?)- Pub-sub patternsendSharded(topic, data, shardKey, totalShards?)- Sharded messaginguse(middleware)- Add middlewareunsubscribe(topic)- Unsubscribe from topicshutdown(options?)- Graceful shutdowngetSubscriptions()- Get active subscriptions
CLI Tool
For quick project scaffolding, use the CLI:
npm install -g @nats-ms/cli
nats-ms init my-serviceSee @nats-ms/cli for details.
Requirements
- Node.js >= 18.0.0
- NATS Server 2.x
Links
License
MIT © 2025 TechnoFrog LLC
