DocumentationReference

HazelJS Config Server Package

npm downloads

@hazeljs/​config-server is a Git-backed configuration server for HazelJS — Spring Cloud Config for TypeScript. One repo, every service, refresh without a restart.

Use @hazeljs/​config for local .env / schema validation. Use this package when many services should read the same Git repo.

Quick Reference

  • Purpose: Clone a config Git repo, overlay application + {app} + {profile} files, decrypt {cipher} secrets, serve over HTTP, and pull updates on demand.
  • When to use: Shared configuration across microservices, environment overlays (dev / staging / prod), secrets in Git as ciphertext, or hot reload without redeploying every service.
  • Key concepts: Git label (branch or tag), property-source overlay order, ConfigServer / ConfigClient, @ConfigValue({ refresh: true }), AES-256-GCM {cipher}v1:..., audit log.
  • Dependencies: @hazeljs/​core. The server process needs a git binary; HTTP clients do not.
  • Common patterns: Run new ConfigServer({ git: { uri }, port: 8888 }).start() → services call ConfigClient.load()client.get('database.url')POST /​refresh or client.refresh() after a Git push.
  • Common mistakes: Using @hazeljs/​config alone when you needed a shared Git repo; committing CONFIG_SERVER_ENCRYPT_KEY next to {cipher} values; expecting a Java .jks keystore (this package uses AES-256-GCM).

Why a config server?

Local .env files do not scale past one process. Spring Cloud Config solved this in the JVM world with a Git repo of YAML and a small HTTP API. @hazeljs/​config-server is that pattern for Node:

  • Services discover config by application name and profile, not by copying files
  • Git is the source of truth (PRs, history, labels)
  • Secrets can live in the repo as {cipher}v1:... and decrypt only when served
  • Clients refresh without a process restart

Architecture

graph LR
  G["Git config repo"] --> S["ConfigServer<br/>clone / fetch / label"]
  S --> H["HTTP<br/>GET /app/profile/label"]
  S --> E["AES-256-GCM<br/>decrypt tree"]
  H --> C["ConfigClient"]
  C --> A["@ConfigValue<br/>refresh: true"]
  
  style G fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style S fill:#6366f1,stroke:#818cf8,stroke-width:2px,color:#fff
  style H fill:#f59e0b,stroke:#fbbf24,stroke-width:2px,color:#fff
  style E fill:#ef4444,stroke:#f87171,stroke-width:2px,color:#fff
  style C fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
  style A fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff

Overlay order (later wins)

  1. application.yml (shared defaults)
  2. application-{profile}.yml
  3. {application}.yml
  4. {application}-{profile}.yml
  5. Extra files under searchPaths (e.g. configs/​{application}/​{profile})

Formats: YAML, JSON, .properties, .env. Nested keys work with dotted getters: database.url.

Installation

npm install @hazeljs/​config-server

Quick Start

Run a server

import { ConfigServer } from '@hazeljs/​config-server';

const server = new ConfigServer({
  git: {
    uri: 'https://github.com/​org/​config-repo',
    searchPaths: ['configs/​{application}/​{profile}'],
    defaultLabel: 'main',
  },
  encryption: {
    enabled: true,
    key: process.env.CONFIG_SERVER_ENCRYPT_KEY,
  },
  port: 8888,
  refreshInterval: 60_000,
});

await server.start();

HTTPS Git: set git.username + git.password (PAT). Local path or file:/​/​ also works. Tests and air-gapped images can use nativePath instead of Git.

Client

import { ConfigClient, ConfigValue } from '@hazeljs/​config-server';

const client = new ConfigClient({
  uri: 'http://localhost:8888',
  application: 'user-service',
  profiles: ['prod'],
  label: 'main',
  refreshInterval: 30_000,
});
await client.load();

client.get('database.url');
await client.refresh();

class AppConfig {
  @ConfigValue('database.url', { refresh: true })
  dbUrl!: string;

  @ConfigValue('features.newAlgorithm', { default: false, type: 'boolean' })
  useNewAlgorithm!: boolean;
}

In-process (no HTTP), pass server instead of uri.

HTTP API

MethodPathPurpose
GET/​{application}/​{profile}Merged environment (profile may be prod,cloud)
GET/​{application}/​{profile}/​{label}Same, pinned to a Git ref
POST/​refreshgit fetch + checkout
POST/​encryptplaintext → {cipher}v1:...
POST/​decryptcipher → plaintext
GET/​healthliveness + current SHA
GET/​auditrecent clone / resolve / encrypt events

Encryption

Java .jks keystores are JVM-specific. This package uses AES-256-GCM. Put the passphrase in CONFIG_SERVER_ENCRYPT_KEY or encryption.keyFile.

const cipher = server.encrypt('my-db-password');
/​/​ database:
/​/​   password: '{cipher}v1:...'

Values are decrypted when the environment is served. Do not commit the key next to the ciphertext.

Recipes

Recipe: Git-backed config server

import { ConfigServer } from '@hazeljs/​config-server';

await new ConfigServer({
  git: { uri: process.env.CONFIG_GIT_URI!, defaultLabel: 'main' },
  encryption: { enabled: true, key: process.env.CONFIG_SERVER_ENCRYPT_KEY },
  port: 8888,
}).start();

Recipe: Config client refresh

import { ConfigClient, ConfigValue } from '@hazeljs/​config-server';

const client = new ConfigClient({
  uri: 'http://config-server:8888',
  application: 'payments',
  profiles: ['prod'],
});
await client.load();

class PaymentsConfig {
  @ConfigValue('stripe.secret', { refresh: true })
  stripeSecret!: string;
}

await client.refresh(); /​/​ after a Git push + POST /​refresh on the server