@dangao/nacos-client
v0.1.2
Published
Nacos 3.X Open API client for Bun runtime
Maintainers
Readme
@dangao/nacos-client
A lightweight Nacos 3.X Open API client for Bun runtime, supporting configuration management and service discovery.
Features
- Configuration Management - Get configurations from Nacos config center
- Service Discovery - Register, deregister, and query service instances
- High Availability - Automatic server failover and retry mechanism
- TypeScript Native - Full TypeScript support with complete type definitions
- Bun Optimized - Designed specifically for Bun runtime
Installation
bun add @dangao/nacos-clientQuick Start
Configuration Management
import { NacosClient, NacosConfigClient } from "@dangao/nacos-client";
// Create client (omit namespaceId for the public namespace)
const client = new NacosClient({
serverList: ["http://localhost:8848"],
username: "nacos",
password: "nacos",
});
const configClient = new NacosConfigClient(client);
// Get configuration
const config = await configClient.getConfig({
dataId: "application.yaml",
groupName: "DEFAULT_GROUP",
});
console.log(config.content); // Configuration content
console.log(config.md5); // Configuration MD5 hash
console.log(config.contentType); // Content typeService Discovery
import { NacosClient, NacosServiceClient } from "@dangao/nacos-client";
const client = new NacosClient({
serverList: ["http://localhost:8848"],
});
const serviceClient = new NacosServiceClient(client);
// Register service instance
await serviceClient.registerInstance({
serviceName: "my-service",
ip: "192.168.1.100",
port: 8080,
weight: 1,
enabled: true,
metadata: {
version: "1.0.0",
env: "production",
},
});
// Query service instances
const instances = await serviceClient.getInstances({
serviceName: "my-service",
healthyOnly: true,
});
console.log(instances);
// Deregister service instance
await serviceClient.deregisterInstance("my-service", "192.168.1.100", 8080);API Reference
NacosClient
The base HTTP client that handles communication with Nacos server.
Constructor Options
| Option | Type | Required | Default | Description |
| ------------- | ---------- | -------- | ------- | ------------------------------------- |
| serverList | string[] | Yes | - | Nacos server addresses |
| namespaceId | string | No | "" | Namespace ID; omit for public (empty string, not the literal "public") |
| username | string | No | - | Username for authentication |
| password | string | No | - | Password for authentication |
| timeout | number | No | 5000 | Request timeout in milliseconds |
| retryCount | number | No | 3 | Number of retry attempts |
| retryDelay | number | No | 1000 | Delay between retries in milliseconds |
NacosConfigClient
Client for configuration management operations.
Methods
getConfig(options: GetConfigOptions): Promise<ConfigResult>
Get configuration from Nacos config center.
Options:
| Field | Type | Required | Description |
| ------------- | -------- | -------- | --------------------------------------- |
| dataId | string | Yes | Configuration ID |
| groupName | string | Yes | Configuration group |
| namespaceId | string | No | Namespace ID (overrides client setting; defaults to "" when omitted) |
Returns: ConfigResult
| Field | Type | Description |
| -------------- | -------- | --------------------------- |
| content | string | Configuration content |
| md5 | string | MD5 hash of the content |
| lastModified | number | Last modification timestamp |
| contentType | string | Content type |
NacosServiceClient
Client for service registration and discovery operations.
Methods
registerInstance(options: RegisterInstanceOptions): Promise<void>
Register a service instance.
Options:
| Field | Type | Required | Description |
| ------------- | ------------------------ | -------- | ----------------------------------- |
| serviceName | string | Yes | Service name |
| ip | string | Yes | Instance IP address |
| port | number | Yes | Instance port |
| weight | number | No | Instance weight |
| enabled | boolean | No | Whether instance is enabled |
| metadata | Record<string, string> | No | Instance metadata |
| clusterName | string | No | Cluster name |
| namespaceId | string | No | Namespace ID |
| groupName | string | No | Group name |
| heartBeat | boolean | No | Whether this is a heartbeat request |
deregisterInstance(serviceName, ip, port, options?): Promise<void>
Deregister a service instance.
Parameters:
| Parameter | Type | Required | Description |
| --------------------- | -------- | -------- | ------------------- |
| serviceName | string | Yes | Service name |
| ip | string | Yes | Instance IP address |
| port | number | Yes | Instance port |
| options.namespaceId | string | No | Namespace ID |
| options.groupName | string | No | Group name |
| options.clusterName | string | No | Cluster name |
getInstances(options: GetInstancesOptions): Promise<ServiceInstance[]>
Query service instances.
Options:
| Field | Type | Required | Description |
| ------------- | --------- | -------- | ----------------------------- |
| serviceName | string | Yes | Service name |
| namespaceId | string | No | Namespace ID |
| groupName | string | No | Group name |
| clusterName | string | No | Cluster name |
| healthyOnly | boolean | No | Only return healthy instances |
Returns: ServiceInstance[]
| Field | Type | Description |
| ------------- | ------------------------ | ----------------- |
| serviceName | string | Service name |
| ip | string | Instance IP |
| port | number | Instance port |
| weight | number | Instance weight |
| healthy | boolean | Health status |
| enabled | boolean | Enabled status |
| metadata | Record<string, string> | Instance metadata |
| clusterName | string | Cluster name |
| namespaceId | string | Namespace ID |
| groupName | string | Group name |
Error Handling
The client throws NacosClientError for all errors:
import { NacosClientError } from "@dangao/nacos-client";
try {
const config = await configClient.getConfig({
dataId: "non-existent",
groupName: "DEFAULT_GROUP",
});
} catch (error) {
if (error instanceof NacosClientError) {
console.error("Nacos error:", error.message);
console.error("Cause:", error.cause);
}
}High Availability
The client supports multiple Nacos servers for high availability:
const client = new NacosClient({
serverList: [
"http://nacos1:8848",
"http://nacos2:8848",
"http://nacos3:8848",
],
retryCount: 3,
retryDelay: 1000,
});When a server is unavailable, the client automatically switches to the next server in the list and retries the request.
Integration with @dangao/bun-server
This package is designed to work seamlessly with the @dangao/bun-server
framework. The framework provides higher-level abstractions through
ConfigCenterModule and ServiceRegistryModule.
Installation
bun add @dangao/bun-serverNote:
@dangao/nacos-clientis included as a dependency of@dangao/bun-server, no need to install separately.
Using ConfigCenterModule
import {
Application,
CONFIG_CENTER_TOKEN,
type ConfigCenter,
ConfigCenterModule,
Controller,
GET,
Inject,
Injectable,
Module,
} from "@dangao/bun-server";
@Injectable()
class AppService {
public constructor(
@Inject(CONFIG_CENTER_TOKEN) private readonly configCenter: ConfigCenter,
) {}
public async getAppConfig(): Promise<string> {
const config = await this.configCenter.getConfig(
"app-config",
"DEFAULT_GROUP",
);
return config.content;
}
public watchConfig(): void {
// Watch for config changes
this.configCenter.watchConfig("app-config", "DEFAULT_GROUP", (result) => {
console.log("Config changed:", result.content);
});
}
}
@Controller("/api")
class AppController {
public constructor(private readonly appService: AppService) {}
@GET("/config")
public async getConfig() {
return { config: await this.appService.getAppConfig() };
}
}
@Module({
imports: [
ConfigCenterModule.forRoot({
provider: "nacos",
nacos: {
client: {
serverList: ["http://localhost:8848"],
username: "nacos",
password: "nacos",
},
watchInterval: 5000, // Config watch interval (ms)
},
}),
],
controllers: [AppController],
providers: [AppService],
})
class AppModule {}
const app = new Application();
app.registerModule(AppModule);
app.listen(3000);Using ServiceRegistryModule
import {
Application,
Controller,
GET,
Inject,
Injectable,
Module,
POST,
SERVICE_REGISTRY_TOKEN,
type ServiceInstance,
type ServiceRegistry,
ServiceRegistryModule,
} from "@dangao/bun-server";
@Injectable()
class DiscoveryService {
public constructor(
@Inject(SERVICE_REGISTRY_TOKEN) private readonly serviceRegistry:
ServiceRegistry,
) {}
public async registerSelf(): Promise<void> {
const instance: ServiceInstance = {
serviceName: "my-service",
ip: "127.0.0.1",
port: 3000,
weight: 1,
healthy: true,
enabled: true,
metadata: { version: "1.0.0" },
};
await this.serviceRegistry.register(instance);
}
public async discoverService(
serviceName: string,
): Promise<ServiceInstance[]> {
return await this.serviceRegistry.getInstances(serviceName);
}
public watchService(serviceName: string): void {
this.serviceRegistry.watchInstances(serviceName, (instances) => {
console.log(`Service ${serviceName} instances:`, instances);
});
}
}
@Controller("/api")
class DiscoveryController {
public constructor(private readonly discoveryService: DiscoveryService) {}
@POST("/register")
public async register() {
await this.discoveryService.registerSelf();
return { message: "Service registered" };
}
@GET("/services/:name")
public async getServices(@Param("name") name: string) {
return await this.discoveryService.discoverService(name);
}
}
@Module({
imports: [
ServiceRegistryModule.forRoot({
provider: "nacos",
nacos: {
client: {
serverList: ["http://localhost:8848"],
username: "nacos",
password: "nacos",
},
watchInterval: 5000, // Instance watch interval (ms)
heartbeatInterval: 5000, // Heartbeat interval (ms)
},
}),
],
controllers: [DiscoveryController],
providers: [DiscoveryService],
})
class AppModule {}
const app = new Application();
app.registerModule(AppModule);
app.listen(3000);Using ServiceClient for Inter-Service Communication
import {
Inject,
Injectable,
RequestLogInterceptor,
ResponseLogInterceptor,
SERVICE_REGISTRY_TOKEN,
ServiceClient,
type ServiceRegistry,
TraceIdRequestInterceptor,
} from "@dangao/bun-server";
@Injectable()
class UserService {
private readonly serviceClient: ServiceClient;
public constructor(
@Inject(SERVICE_REGISTRY_TOKEN) serviceRegistry: ServiceRegistry,
) {
this.serviceClient = new ServiceClient(serviceRegistry);
// Add interceptors
this.serviceClient.addRequestInterceptor(new TraceIdRequestInterceptor());
this.serviceClient.addRequestInterceptor(new RequestLogInterceptor());
this.serviceClient.addResponseInterceptor(new ResponseLogInterceptor());
}
public async getUser(id: string): Promise<unknown> {
const response = await this.serviceClient.call({
serviceName: "user-service",
method: "GET",
path: `/api/users/${id}`,
loadBalanceStrategy: "roundRobin",
timeout: 5000,
// Circuit breaker support
enableCircuitBreaker: true,
fallback: () => ({ id, name: "Fallback User" }),
});
return response.data;
}
public async createUser(data: unknown): Promise<unknown> {
const response = await this.serviceClient.call({
serviceName: "user-service",
method: "POST",
path: "/api/users",
body: data,
loadBalanceStrategy: "weightedRoundRobin",
// Rate limiting support
enableRateLimit: true,
rateLimitKey: "user-service-create",
});
return response.data;
}
}Load Balancing Strategies
The framework supports multiple load balancing strategies:
random- Random selectionroundRobin- Round-robin selectionweightedRoundRobin- Weighted round-robin based on instance weightsconsistentHash- Consistent hashing (requiresconsistentHashKey)leastActive- Least active connections
Service Governance Features
// Configure circuit breaker
serviceClient.setDefaultCircuitBreakerOptions({
failureThreshold: 0.5, // 50% failure rate triggers circuit open
timeWindow: 60000, // Time window for failure calculation (ms)
minimumRequests: 10, // Minimum requests before circuit can open
openDuration: 60000, // Duration circuit stays open (ms)
halfOpenRequests: 3, // Requests allowed in half-open state
});
// Configure rate limiter
serviceClient.setDefaultRateLimiterOptions({
requestsPerSecond: 100,
timeWindow: 1000,
});
// Configure retry strategy
serviceClient.setDefaultRetryStrategy({
maxRetries: 3,
retryDelay: 1000,
exponentialBackoff: true,
baseDelay: 1000,
maxDelay: 30000,
});Namespace
Nacos 3.x Open API requires every request to include a namespaceId parameter. The default namespace shown as public in the console maps to an empty string "", not the literal "public".
- When
namespaceIdis not set onNacosClient, the client automatically sendsnamespaceId=(empty value) ongetConfig,registerInstance,deregisterInstance,getInstances, and related calls - For a custom namespace, pass the namespace ID from the console (usually a UUID)
- 0.1.1 and earlier omitted the parameter when unset, causing Nacos 3.x to return
code=20004; use 0.1.2+ - If you patched
@dangao/[email protected]withbun patch, removepatchedDependenciesand the patch file after upgrading
// Public namespace (recommended: omit namespaceId)
const publicClient = new NacosClient({ serverList: ["http://localhost:8848"] });
// Custom namespace
const customClient = new NacosClient({
serverList: ["http://localhost:8848"],
namespaceId: "your-namespace-uuid",
});Nacos Compatibility
This client is designed for Nacos 3.X and uses the following Open API endpoints:
- Configuration:
GET /nacos/v3/client/cs/config - Service Registration:
POST /nacos/v3/client/ns/instance - Service Deregistration:
DELETE /nacos/v3/client/ns/instance - Service Discovery:
GET /nacos/v3/client/ns/instance/list
Related Documentation
License
MIT
