Hot-reloadable public configuration management for Apps with GitHub webhook integration
npm install @1matrix/config-loaderHot-reloadable public configuration management for Applications with GitHub webhook integration.
- ✅ Hot Reloading: Automatically update configurations without restarting your application
- ✅ GitHub Integration: Fetch configurations from a dedicated GitHub repository
- ✅ Webhook Support: Real-time updates via GitHub webhooks
- ✅ Polling Backup: Periodic polling as a fallback mechanism
- ✅ Schema Validation: JSON Schema validation for all configurations
- ✅ Environment Support: Map NODE_ENV to different configuration directories
- ✅ Fail-safe Caching: Local cache fallback when GitHub is unavailable
- ✅ TypeScript: Full TypeScript support with type definitions
- ✅ Event-Driven: Listen to configuration changes with EventEmitter
- ✅ Framework Adapters: Built-in Express and Fastify support
- ✅ API Key Auth: Ready-to-use API key authentication middleware
``bash`
pnpm add @1matrix/config-loader
`typescript
import { ConfigLoader } from "@1matrix/config-loader";
// Set NODE_ENV (required)
process.env.NODE_ENV = "production";
const config = new ConfigLoader({
repository: "OneMatrixL1/public-configs",
branch: "main",
githubToken: process.env.GITHUB_TOKEN,
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
// Optional settings
pollingInterval: 5 60 1000, // Optional: 5 minutes (disabled by default)
envMappings: { production: "prod", development: "dev" }, // Optional: map NODE_ENV values
cacheFile: "/tmp/config-loader-cache.json", // Optional: default location
requireInitialConfig: false, // Optional: false = permissive mode (default)
});
await config.initialize();
`
`typescript
// Get specific value
const apiKeyName = config.get("api-keys.a129f786...");
// Get with type inference
const handlers = config.get
// Check if exists
if (config.has("api-keys.some-hash")) {
// ...
}
// Get all configs
const allConfigs = config.getAll();
`
`typescript
// Listen to all updates
config.on("update", (newConfigs, changedKeys) => {
console.log("Updated configs:", changedKeys);
});
// Listen to specific config updates
config.on("update:api-keys", (newApiKeys) => {
console.log("API keys changed");
});
// Listen to validation errors
config.on("validation-error", (configName, errors) => {
console.error("Validation failed:", configName, errors);
});
// Listen to error events (for monitoring)
config.on("error", (errorEvent) => {
console.error("Config error:", {
source: errorEvent.source, // 'github' | 'cache' | 'validation'
phase: errorEvent.phase, // 'initialize' | 'refresh' | 'polling'
error: errorEvent.error,
fallbackUsed: errorEvent.fallbackUsed,
timestamp: errorEvent.timestamp,
});
});
`
Express:
`typescript
import express from "express";
import { createExpressWebhook } from "@1matrix/config-loader";
const app = express();
app.post(
"/webhook/config",
express.json(),
createExpressWebhook(config, {
onUpdate: (configs) => console.log("Updated via webhook"),
onError: (err) => console.error(err),
})
);
`
Fastify:
`typescript
import Fastify from "fastify";
import { createFastifyWebhook } from "@1matrix/config-loader";
const fastify = Fastify();
fastify.post("/webhook/config", createFastifyWebhook(config, {
onUpdate: (configs) => console.log("Updated via webhook"),
onError: (err) => console.error(err),
}));
`
Express:
`typescript
import { createExpressApiKeyAuth } from "@1matrix/config-loader";
// Default: uses Keccak256 hashing (most common)
app.use("/api", createExpressApiKeyAuth(config));
// If clients send pre-hashed keys (disable hashing)
app.use("/api", createExpressApiKeyAuth(config, { hashFn: null }));
app.get("/api/protected", (req, res) => {
res.json({ authenticatedAs: req.apiKeyName });
});
`
Fastify:
`typescript
import { createFastifyApiKeyAuth } from "@1matrix/config-loader";
// Default: uses Keccak256 hashing (most common)
fastify.addHook("preHandler", createFastifyApiKeyAuth(config));
// If clients send pre-hashed keys (disable hashing)
fastify.addHook("preHandler", createFastifyApiKeyAuth(config, { hashFn: null }));
// Or apply to specific routes
fastify.get("/api/protected", {
preHandler: createFastifyApiKeyAuth(config)
}, async (request) => {
return { authenticatedAs: request.apiKeyName };
});
`
Your GitHub configuration repository should follow this structure:
``
public-configs/
├── schemas/
│ ├── api-keys.schema.json
│ ├── roles.schema.json
│ └── intent-handlers.schema.json
├── dev/
│ ├── api-keys.json
│ ├── roles.json
│ └── intent-handlers.json
├── staging/
│ └── ...
└── prod/
└── ...
api/keys-dev.json
`json`
{
"a129f7860d47c0630e6d06c153fe36711f25a31bfb4304dc6d2a79e609da0e96": "VNIDC",
"b234c8971e58d1741f7e17d264gf47822g36b42cgc5415ed7e3b8a710eb1fa07": "SERVICE_2"
}
rabc/roles-dev.json
`json`
{
"explorer.viewer": [
"0xE61383556642AF1Bd7c5756b13f19A63Dc8601df",
"0x7d5538fEe2CE89dA936ec29cC48386b6E7548FaB"
],
"admin": ["0x194f5b1755562966302Ef0BbF4349c842c60FC42"]
}
intent/handlers-dev.json
`json`
[
"0x84f915BcbD5C1134BCb93a0f50D9D36E6D3b508c",
"0x626b1E2458A9307E73A570c291bCd467216cc1D7"
]
schemas/api-keys.schema.json
`json`
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"patternProperties": {
"^[a-f0-9]{64}$": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
}
The NODE_ENV environment variable determines which configuration directory to load.
`bash`
NODE_ENV=production node app.js
Use envMappings to map NODE_ENV values to configuration directories:
`typescript`
const config = new ConfigLoader({
repository: "owner/repo",
branch: "main",
envMappings: {
production: "prod",
development: "dev",
staging: "stg",
},
});
Set SKIP_AUTH=true to bypass authentication middleware (for testing only).
`bash`
SKIP_AUTH=true NODE_ENV=test npm test
⚠️ Warning: If SKIP_AUTH=true in production, a warning will be logged to the console.
| Option | Type | Default | Description |
| ----------------------- | ------------------------- | --------------------------------- | ------------------------------------------------ |
| repository | string | Required | GitHub repository in owner/repo format |branch
| | string | Required | Git branch to fetch from |githubToken
| | string | undefined | GitHub personal access token |webhookSecret
| | string | undefined | GitHub webhook secret for signature verification |envMappings
| | Record | {} | Map NODE_ENV values to config directories |pollingInterval
| | number | undefined (disabled) | Polling interval in milliseconds |cacheFile
| | string | /tmp/config-loader-cache.json | Local cache file path |usePackageSchemas
| | boolean | true | Use embedded default schemas |requireInitialConfig
| | boolean | false | Throw error if no configs available on startup |
Permissive Mode (requireInitialConfig: false, default):
- Starts with empty configs if GitHub and cache both fail
- Emits error events for monitoring
- Application continues running
Strict Mode (requireInitialConfig: true):
- Throws error if no configs available on startup
- Ensures application always has configuration data
- Better for production deployments with critical config dependencies
#### createExpressWebhook(configLoader, options?)
Create Express middleware for handling GitHub webhook requests.
Parameters:
- configLoader: ConfigLoader - ConfigLoader instanceoptions?: WebhookCoreOptions
- - Optional webhook handler optionsonUpdate?: (configs) => void
- - Called when configs are updatedonError?: (error) => void
- - Called on errors
Returns: Express middleware function
#### createFastifyWebhook(configLoader, options?)
Create Fastify route handler for GitHub webhook requests.
Parameters: Same as createExpressWebhook
Returns: Fastify route handler function
#### createExpressApiKeyAuth(configLoader, options?)
Create Express middleware for API key authentication.
Parameters:
- configLoader: ConfigLoader - ConfigLoader instanceoptions?: ExpressApiKeyAuthOptions
- headerName?: string
- - Header to extract API key from (default: x-api-key)hashFn?: (raw: string) => string
- - Function to hash raw API keysonError?: (req, res, error) => void
- - Custom error handleronSuccess?: (req, res, name) => void
- - Custom success handler
Returns: Express middleware function
Note: Attaches apiKeyName to req object when authentication succeeds.
#### createFastifyApiKeyAuth(configLoader, options?)
Create Fastify preHandler hook for API key authentication.
Parameters:
- configLoader: ConfigLoader - ConfigLoader instanceoptions?: FastifyApiKeyAuthOptions
- headerName?: string
- - Header to extract API key from (default: x-api-key)hashFn?: (raw: string) => string
- - Function to hash raw API keysonError?: (request, reply, error) => void
- - Custom error handleronSuccess?: (request, reply, name) => void
- - Custom success handler
Returns: Fastify preHandler hook function
Note: Attaches apiKeyName to request object when authentication succeeds.
#### verifyApiKey(configLoader, hashedKey)
Low-level API key verification utility (used internally by auth adapters).
Parameters:
- configLoader: ConfigLoader - ConfigLoader instancehashedKey: string | undefined
- - Pre-hashed API key
Returns: ApiKeyVerificationResult`typescript`
{
valid: boolean;
name?: string; // API key name if valid
error?: string; // Error message if invalid
}
`typescript`
interface ConfigLoaderOptions {
repository: string; // GitHub repository (owner/repo)
branch: string; // Branch to fetch from
githubToken?: string; // GitHub personal access token
webhookSecret?: string; // GitHub webhook secret
envMappings?: Record
defaultEnv?: string; // Default environment (default: 'dev')
pollingInterval?: number; // Polling interval in ms (default: 300000)
cacheFile?: string; // Cache file path (default: './config-cache.json')
usePackageSchemas?: boolean; // Use built-in schemas (default: true)
}
#### async initialize(): Promise
Initialize the config loader. Attempts to load from GitHub, falls back to cache if unavailable.
#### async refresh(): Promise
Manually refresh configurations from GitHub.
#### get
Get a configuration value by dot-notation path.
#### has(path: string): boolean
Check if a configuration path exists.
#### getAll(): Record
Get all configurations.
#### getCurrentEnvironment(): string
Get the current environment name.
#### getConfigNames(): string[]
Get names of all loaded configurations.
#### createWebhookHandler(options?: WebhookHandlerOptions): ExpressMiddleware
Create an Express middleware for handling GitHub webhooks.
#### destroy(): void
Clean up resources (stop polling, remove listeners).
#### 'update'
Emitted when configurations are updated.
`typescript`
config.on(
"update",
(newConfigs: Record
// Handle update
}
);
#### 'update:${configName}'
Emitted when a specific configuration is updated.
`typescript`
config.on("update:api-keys", (newApiKeys: any) => {
// Handle API keys update
});
#### 'validation-error'
Emitted when configuration validation fails.
`typescript`
config.on("validation-error", (configName: string, errors: any[]) => {
// Handle validation error
});
#### 'error'
Emitted when an error occurs.
`typescript`
config.on("error", (error: Error) => {
// Handle error
});
1. Go to your config repository → Settings → Webhooks → Add webhook
2. Payload URL: https://your-app.com/webhook/config-updateapplication/json
3. Content type:
4. Secret: Generate a strong random secret
5. Events: Select "Just the push event"
6. Active: ✅
Ensure only authorized users can update configs:
1. Go to Settings → Branches → Add rule
2. Branch name pattern: main
3. Enable:
- Require pull request reviews (at least 1 approval)
- Require status checks before merging
- Restrict push access to admins
`bash`
GITHUB_TOKEN=ghp_your_personal_access_token
GITHUB_WEBHOOK_SECRET=your_webhook_secret
Map NODE_ENV values to configuration directories:
`typescript`
const config = new ConfigLoader({
// ...
envMappings: {
dev: "dev",
development: "dev",
develop: "develop",
stg: "stg",
staging: "staging",
prod: "prod",
production: "production",
},
defaultEnv: "dev",
});
- Store only hashed/public data in configurations (e.g., Keccak256 hashes)
- Use environment variables for webhook secrets and GitHub tokens
- Enable branch protection on configuration repository
- Monitor validation-error events for malicious payloads
- Set up alerts for repeated webhook validation failures
- Store raw API keys or passwords in configuration files
- Expose webhook endpoints without signature validation
- Allow direct pushes to main branch (require PRs)
- Disable schema validation
- Use weak webhook secrets
`bashRun tests
pnpm test
Building
`bash
Build TypeScript to JavaScript
pnpm buildBuild and watch for changes
pnpm build:watch
`Examples
See the
examples/ directory for complete examples:-
basic-usage.ts: Simple configuration loading
- express-integration.ts`: Full Express.js integration with webhooksMIT
Contributions are welcome! Please open an issue or submit a pull request.
For issues and questions, please open an issue on GitHub.