HazelJS Config Server Package
@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 agitbinary; HTTP clients do not. - Common patterns: Run
new ConfigServer({ git: { uri }, port: 8888 }).start()→ services callConfigClient.load()→client.get('database.url')→POST /refreshorclient.refresh()after a Git push. - Common mistakes: Using
@hazeljs/configalone when you needed a shared Git repo; committingCONFIG_SERVER_ENCRYPT_KEYnext to{cipher}values; expecting a Java.jkskeystore (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)
application.yml(shared defaults)application-{profile}.yml{application}.yml{application}-{profile}.yml- 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
| Method | Path | Purpose |
|---|---|---|
| GET | /{application}/{profile} | Merged environment (profile may be prod,cloud) |
| GET | /{application}/{profile}/{label} | Same, pinned to a Git ref |
| POST | /refresh | git fetch + checkout |
| POST | /encrypt | plaintext → {cipher}v1:... |
| POST | /decrypt | cipher → plaintext |
| GET | /health | liveness + current SHA |
| GET | /audit | recent 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
Related Resources
- Blog: Production Ops Stack — Config Server, Self-Healing, and Predictive Scaling together
- Config Package — local
.envand schema validation - Discovery Package — find services by name
- CLI —
hazel add config-server