npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dangao/nacos-client

v0.1.2

Published

Nacos 3.X Open API client for Bun runtime

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-client

Quick 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 type

Service 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-server

Note: @dangao/nacos-client is 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 selection
  • roundRobin - Round-robin selection
  • weightedRoundRobin - Weighted round-robin based on instance weights
  • consistentHash - Consistent hashing (requires consistentHashKey)
  • 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 namespaceId is not set on NacosClient, the client automatically sends namespaceId= (empty value) on getConfig, 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] with bun patch, remove patchedDependencies and 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