
Security News
npm Tooling Bug Incorrectly Marks One-Character Packages as Security Holders
npm confirmed a tooling bug incorrectly marked several one-character packages as security holders and said it was working on a rollback.
@hazeljs/discovery
Advanced tools
Service discovery and registry for HazelJS microservices - Eureka-inspired with multiple backend support
Microservices that find each other.
Service registry, health checks, load balancing. Round Robin, Least Connections, Zone Aware — 6 strategies. Memory, Redis, Consul, or Kubernetes. Eureka-style, without the Java.
npm install @hazeljs/discovery
Install the backend you need:
# Redis backend
npm install ioredis
# Consul backend
npm install consul
# Kubernetes backend
npm install @kubernetes/client-node
import { ServiceRegistry } from '@hazeljs/discovery';
const registry = new ServiceRegistry({
name: 'user-service',
port: 3000,
host: 'localhost',
healthCheckPath: '/health',
healthCheckInterval: 30000,
metadata: { version: '1.0.0' },
zone: 'us-east-1',
tags: ['api', 'users'],
});
await registry.register();
// On shutdown
await registry.deregister();
import { DiscoveryClient } from '@hazeljs/discovery';
const client = new DiscoveryClient({
cacheEnabled: true,
cacheTTL: 30000,
refreshInterval: 15000, // auto-refresh cache every 15s
});
// Get all instances
const instances = await client.getInstances('user-service');
// Get one instance with load balancing
const instance = await client.getInstance('user-service', 'round-robin');
// On shutdown
client.close();
import { ServiceClient } from '@hazeljs/discovery';
const serviceClient = new ServiceClient(discoveryClient, {
serviceName: 'user-service',
loadBalancingStrategy: 'round-robin',
timeout: 5000,
retries: 3,
retryDelay: 1000,
});
// Automatic service discovery + load balancing + smart retries
const user = await serviceClient.get('/users/123');
const created = await serviceClient.post('/users', { name: 'John' });
import { ServiceRegistryDecorator, InjectServiceClient } from '@hazeljs/discovery';
@ServiceRegistryDecorator({
name: 'order-service',
port: 3001,
healthCheckPath: '/health',
})
export class AppModule {}
@Injectable()
export class OrderService {
constructor(
@InjectServiceClient('user-service')
private userClient: ServiceClient
) {}
async createOrder(userId: string) {
const user = await this.userClient.get(`/users/${userId}`);
// ... create order
}
}
const instance = await client.getInstance('service-name', 'round-robin');
const instance = await client.getInstance('service-name', 'random');
Tracks active connections per instance. When used with ServiceClient, connection counts are automatically incremented/decremented on each request.
const instance = await client.getInstance('service-name', 'least-connections');
// Set weight in service metadata
const registry = new ServiceRegistry({
name: 'api-service',
port: 3000,
metadata: { weight: 5 }, // Higher weight = more traffic
});
const instance = await client.getInstance('service-name', 'ip-hash');
const factory = client.getLoadBalancerFactory();
const strategy = factory.create('zone-aware', { zone: 'us-east-1' });
import { ServiceStatus } from '@hazeljs/discovery';
const instances = await client.getInstances('user-service', {
zone: 'us-east-1',
status: ServiceStatus.UP,
tags: ['api', 'production'],
metadata: { version: '2.0.0' },
});
The applyServiceFilter utility is also exported for use in custom backends or application code:
import { applyServiceFilter } from '@hazeljs/discovery';
const filtered = applyServiceFilter(instances, { zone: 'us-east-1' });
The default backend. Stores everything in-process memory -- suitable for development and testing.
import { MemoryRegistryBackend } from '@hazeljs/discovery';
const backend = new MemoryRegistryBackend(90000); // optional expiration in ms
const registry = new ServiceRegistry(config, backend);
Distributed registry using Redis with TTL-based expiration. Uses SCAN (not KEYS) for production safety and MGET for efficient batch lookups. Includes connection error handling with automatic reconnection support.
npm install ioredis
import Redis from 'ioredis';
import { RedisRegistryBackend } from '@hazeljs/discovery';
const redis = new Redis({
host: 'localhost',
port: 6379,
password: 'your-password',
});
const backend = new RedisRegistryBackend(redis, {
keyPrefix: 'myapp:discovery:', // default: 'hazeljs:discovery:'
ttl: 90, // seconds, default: 90
});
const registry = new ServiceRegistry(config, backend);
// On shutdown
await backend.close();
Integrates with HashiCorp Consul using TTL-based health checks.
npm install consul
import Consul from 'consul';
import { ConsulRegistryBackend } from '@hazeljs/discovery';
const consul = new Consul({
host: 'localhost',
port: 8500,
});
const backend = new ConsulRegistryBackend(consul, {
ttl: '30s', // TTL check interval (supports "30s", "5m", "1h")
datacenter: 'dc1',
});
const registry = new ServiceRegistry(config, backend);
// On shutdown
await backend.close();
Read-only discovery backend that integrates with Kubernetes Endpoints API. Registration, deregistration, heartbeat, and status updates are no-ops since Kubernetes manages these through its own primitives (Services, Endpoints, probes).
npm install @kubernetes/client-node
import { KubeConfig } from '@kubernetes/client-node';
import { KubernetesRegistryBackend } from '@hazeljs/discovery';
const kubeConfig = new KubeConfig();
kubeConfig.loadFromDefault();
const backend = new KubernetesRegistryBackend(kubeConfig, {
namespace: 'default',
labelSelector: 'app.kubernetes.io/managed-by=hazeljs',
});
// Use the backend for service discovery only
const client = new DiscoveryClient({}, backend);
ServiceClient only retries on transient errors. Client errors (4xx) are thrown immediately without wasting retries:
| Error Type | Retried? |
|---|---|
| Network errors (ECONNREFUSED, timeout) | Yes |
| 502 Bad Gateway | Yes |
| 503 Service Unavailable | Yes |
| 504 Gateway Timeout | Yes |
| 408 Request Timeout | Yes |
| 429 Too Many Requests | Yes |
| 400 Bad Request | No |
| 401 Unauthorized | No |
| 403 Forbidden | No |
| 404 Not Found | No |
| Other 4xx | No |
By default, the package logs to the console with a [discovery] prefix. You can plug in your own logger (e.g., Winston, Pino, Bunyan):
import { DiscoveryLogger } from '@hazeljs/discovery';
DiscoveryLogger.setLogger({
debug: (msg, ...args) => myLogger.debug(msg, ...args),
info: (msg, ...args) => myLogger.info(msg, ...args),
warn: (msg, ...args) => myLogger.warn(msg, ...args),
error: (msg, ...args) => myLogger.error(msg, ...args),
});
// Reset to default console logger
DiscoveryLogger.resetLogger();
All configuration objects are validated at construction time. Invalid configs throw a ConfigValidationError with a descriptive message:
import { ServiceRegistry, ConfigValidationError } from '@hazeljs/discovery';
try {
const registry = new ServiceRegistry({
name: '', // invalid: empty string
port: -1, // invalid: negative port
});
} catch (error) {
if (error instanceof ConfigValidationError) {
console.error(error.message);
// => 'ServiceRegistryConfig: "name" is required and must be a non-empty string'
}
}
class ServiceRegistry {
constructor(config: ServiceRegistryConfig, backend?: RegistryBackend);
register(): Promise<void>;
deregister(): Promise<void>;
getInstance(): ServiceInstance | null;
getBackend(): RegistryBackend;
}
class DiscoveryClient {
constructor(config?: DiscoveryClientConfig, backend?: RegistryBackend);
getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
getInstance(serviceName: string, strategy?: string, filter?: ServiceFilter): Promise<ServiceInstance | null>;
getAllServices(): Promise<string[]>;
clearCache(serviceName?: string): void;
getLoadBalancerFactory(): LoadBalancerFactory;
close(): void;
}
class ServiceClient {
constructor(discoveryClient: DiscoveryClient, config: ServiceClientConfig);
get<T>(path: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
post<T>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
put<T>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
delete<T>(path: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
patch<T>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
}
interface ServiceRegistryConfig {
name: string;
port: number;
host?: string;
protocol?: 'http' | 'https' | 'grpc';
healthCheckPath?: string; // default: '/health'
healthCheckInterval?: number; // default: 30000 (ms)
metadata?: Record<string, unknown>;
zone?: string;
tags?: string[];
}
interface DiscoveryClientConfig {
cacheEnabled?: boolean;
cacheTTL?: number; // default: 30000 (ms)
refreshInterval?: number; // auto-refresh cache interval (ms)
}
interface ServiceClientConfig {
serviceName: string;
loadBalancingStrategy?: string; // default: 'round-robin'
filter?: ServiceFilter;
timeout?: number; // default: 5000 (ms)
retries?: number; // default: 3
retryDelay?: number; // default: 1000 (ms)
}
See the examples directory for complete working examples.
npm test
The package includes 145+ unit tests across 9 test suites with 85%+ code coverage.
Contributions are welcome! Please read our Contributing Guide for details.
Apache 2.0 © HazelJS
FAQs
Service discovery and registry for HazelJS microservices - Eureka-inspired with multiple backend support
The npm package @hazeljs/discovery receives a total of 155 weekly downloads. As such, @hazeljs/discovery popularity was classified as not popular.
We found that @hazeljs/discovery demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
npm confirmed a tooling bug incorrectly marked several one-character packages as security holders and said it was working on a rollback.

Research
/Security News
Newer packages in this compromise use native extensions and .pth loaders to execute JavaScript stealers in developer environments.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.