iq-config
v0.2.7
Published
Easy configuration for your deployments. Elevate your .env files to the next level.
Downloads
319
Maintainers
Readme
iq-config
Next-Level Configuration Management
iq-config
is an easy-to-use configuration utility designed to streamline and
simplify working with configuration values in Node.js applications. It brings
.env
files to the next level by providing type safety, runtime visibility,
and structured configuration handling.
Why Use iq-config?
Managing configurations in different deployment environments—On-Premise,
SaaS (Single Tenant & Multi-Tenant) — can quickly become complex and
error-prone.iq-config
solves this by offering:
- ✅ Fully Type-Safe: Configuration values are strictly typed and validated.
- ✅ Seamless TypeScript Support: Get full code completion and editor integration.
- ✅ Self-Documenting: Every configuration value includes descriptions and categorization.
- ✅ Transparent & Editable at Runtime: View and modify configurations dynamically.
- ✅ Works in Node.js & Browser: Use it anywhere without hassle.
- ✅ Lightweight & Minimal Dependencies: No unnecessary bloat.
- ✅ (Optional) UI Integration: Easily inspect and edit configurations visually (coming soon!).
📦 Installation
You can install iq-config
via npm
or yarn
:
npm install iq-config
# or
yarn add iq-config
🔧 Getting Started
1️⃣ Declare Configuration Schema
First, create a ConfigBuilder
instance and declare your configuration structure:
import { ConfigBuilder } from "iq-config";
const builder = new ConfigBuilder();
const creator = builder.declare({
port: builder.variable(
"port",
"SERVER_PORT",
"The server port",
"Server",
"required",
"static",
3000,
),
url: builder.variable(
"url",
"SERVER_URL",
"The server URL",
"Server",
"required",
"static",
"http://localhost",
),
});
// Define type for the configuration
export type Config = MakeConfigType<typeof creator>;
2️⃣ Initialize Configuration
Next, load configuration values from different sources and build the final configuration:
import fs from "fs-extra";
import dotenv from "dotenv";
export let config: Config;
export const initializeConfig = async () => {
dotenv.config();
const custom = await fs.readJson("path/to/config.json");
const user = await fs.readJson("path/to/user-config.json");
const result = creator.build([process.env, custom, user]);
if (result.status === "Error") {
throw new Error(
`Failed to initialize configuration: ${result.errors.join("; ")}`,
);
}
config = result.config;
};
📌 Features & API Overview
Initializing and Declaring the Config-System
First create a ConfigBuilder
instance to declare the configuration values
used.
import { ConfigBuilder } from "iq-config";
const builder = new ConfigBuilder();
const creator = builder.declare({
// list your config here
});
After declaring the configuration, you can define the Type of your configuration
using the MakeConfigType
helper. This allows you define a global variable to
store the configuration, after initializing your system
type Config = MakeConfigType<typeof creator>;
The steps "declaring" and "creating the type" usually are done in a JS Module, the building in an initialization method.
Creating the Configuration
You now can use the creator
to create an instance of the configuration. This
usually is done in an initialization method, so you can access asynchronous
resources:
let config: Config = undefined as any;
// lying, to not have to deal with null checks later in code.
export const initialize = async () => {
// Load all config sources, e.g.:
dotenv.config();
const custom = await fs.readJson("path/to/config.json");
const user = await fs.readJson("path/to/config.json");
// Then build the config:
let result = creator.build([process.env, custom, user]);
if (result.status === "Error") {
throw new Error(
`Failed to initialize the environment module: ${result.errors.join("; ")}`,
);
}
config = result.config;
};
export const env = (): Config => {
if (!config) {
throw new Error("Initialize the environment module first");
}
return env;
};
Persisting Changes
The build option also accepts a callback to save configuration changes. The callback will be called every time a value was changed, but it will only include the changed values.
If you want to store the changes in a file, you have to merge the new changes with existing ones, ot not loose data.
let result = creator.build(
[...],
async (config) => {
const settings = {
...(await fs.readJson("path/to/config.json")),
...config,
};
await fs.writeJson("path/to/config.json", user, {
spaces: 2,
});
}
);
Declaring Variables
The builder allows to declare strictly typed configuration variables:
builder.variable(
"url", // The variable type
"SERVER_PORT", // The variable key, used to access and read the value
"The server port", // The description, supports Markdown
"Server", // Category, allows grouping of variables
"required", // Required or optional, a required variable cannot be undefined
"static", // Static variables cannot be changed at runtime.
"http://google.de", // Optional default value, if the variable is required
// and no default value was provided, the variable has
// to be supplied during the building.
// Optional: Custom verification method. The method gets the full
// initialized configuration. If no error was found, return undefined,
// otherwise return the description of the error.
(config) => {
return "ERROR"
}
),
The system supports the following types:
- string
- ip
- ipv4
- ipv6
- url
- number
- int
- port
If you need at type that is missing here, please create a ticket.
Using the system:
Reading
The config allows reading, listing and changing values:
Reading a single value:
config.get("KEY_NAME");
Reading a multiple values:
config.mGet(["KEY1_NAME", "KEY2", ...]);
Changing
Change a value. The return is either { success: true }
, or it will return
{ success: false, error: "string"}
, giving a reason for failing. Common
reasons are:
- failing a validator, e.g. giving an invalid IP for an IP variable
- failing a custom validator
- setting null or undefined to a required variable
- changing a readonly variable
await config.set("KEY_NAME", NEW_VALUE);
Changing multiple types at once. mSet
will first apply all changes, then
perform validations on the changed object and only apply the changes if no
validation errors were found. This is required when multiple values are
connected.
For example: ENABLE_CACHE=TRUE; REDIS_CONNECTION_STRING=xxx
. Once the cache
is enabled, the connection string is required.
await config.mSet({
ENABLE_CACHE: true,
REDIS_CONNECTION_STRING: "xxx",
});
Subscribing
The system allows to subscribe to configuration changes. The subscription is to a set of configuration keys. If one or multiple of those keys are changed, the callback will be called, with the subscribed keys as argument.
config.subscribe(["ENABLE_CACHE", "REDIS_CONNECTION_STRING"], async (cfg) => {
console.log(cfg.ENABLE_CACHE);
console.log(cfg.REDIS_CONNECTION_STRING);
});
🛠 Open Points & Future Enhancements
Highest priority, to to bottom.
- Automate CI/CD pipeline for releases
- Improve validator syntax, maybe not attach the validator to single keys, and register them after the declaration, so the type of the config object is known already.
- Optimize subscription handling for better performance
- Remove
ip-address
dependency (only used for IP validation). The goal is 0 dependencies
Why Another Configuration Library?
During one of my projects, the requirements changed, forcing us to support Node.js across multiple deployment models—On-Premise, SaaS Single Tenant, and Multi-Tenant.
Managing diverse configuration options became increasingly complex, leading to inefficiencies and wasted time.
To address this, I needed a configuration system that met the following criteria:
- ✅ Fully Type-Safe – Ensures configuration values are validated and verified.
- ✅ Seamless TypeScript Support – Provides full code completion and editor integration.
- ✅ Easy to Use – Simple API for defining, managing, and retrieving configurations.
- ✅ Self-Describing – Allows listing all values with descriptions.
- ✅ Transparent – Configuration values remain visible at runtime.
- ✅ Editable at Runtime – Supports dynamic modifications.
- ✅ Cross-Platform – Works in both Node.js and the browser.
- ✅ Lightweight – Minimal dependencies for efficiency.
- ✅ (Optional) UI Support – Enables visual inspection and editing (planned as a separate package).
After failing to find an existing library that met all these needs, I decided
to build my own. If it helped me, maybe it can help others too!
Most existing configuration libraries fall short in one or more areas—whether
it's lack of type safety, difficulty in managing configurations dynamically,
or limited editor support. iq-config
was built to solve real-world problems
in large-scale deployments, making it a powerful yet lightweight choice for
your projects.
Give it a try and simplify your configuration management! 🚀