@buzo/jenv
v1.0.0
Published
JENV - JavaScript Environment Variables with built-in templating, filters, and resolvers
Maintainers
Readme
endpoint-env
Compile API endpoint URLs from environment variables, using parameters, filters, and dynamic expressions.
Define your endpoints as templated strings in process.env, then build real URLs from them at runtime — with type coercion, defaults, string transforms, and live values like dates, timestamps, and UUIDs baked right in.
npm install endpoint-envQuick Start
const endpoint = require('endpoint-env');
process.env.ENDPOINT_MATCH = 'https://api.example.com/matches/{matchId}';
const url = endpoint.build('match', { matchId: 12345 });
console.log(url);
// https://api.example.com/matches/12345Endpoints are read from environment variables prefixed with ENDPOINT_ by default. The part of the variable name after the prefix (lowercased) becomes the "scope" you pass to build().
Loading a .env file
endpoint-env uses the official dotenv package for loading .env files, with additional support for variable interpolation.
Pass env: true when constructing an instance to load the default .env file from your current working directory before anything else runs:
const { EndpointEnv } = require('endpoint-env');
const endpoint = new EndpointEnv({
env: true
});
// process.env is now populated from .envYou can pass the same options dotenv.config() accepts:
const endpoint = new EndpointEnv({
env: {
path: '.env.production',
override: true,
debug: true
}
});A bare path string is shorthand for { path: ... }:
const endpoint = new EndpointEnv({
env: '.env.production'
});And you can load multiple files in one go by passing an array:
const endpoint = new EndpointEnv({
env: [{ path: '.env' }, { path: '.env.local' }]
});Variable Interpolation
Variable interpolation is enabled by default, allowing you to reference other variables:
# .env
BASE_URL=https://api.example.com
ENDPOINT_USERS=${BASE_URL}/v1/users
ENDPOINT_MATCHES=${BASE_URL}/v1/matches/{matchId}To disable interpolation:
const endpoint = new EndpointEnv({
env: {
path: '.env',
interpolate: false
}
});Standalone dotenv usage
If you just want the loader itself, it's exported directly:
const { config } = require('endpoint-env');
config(); // loads ./.env
config({ path: '.env.local' }); // loads a specific fileSecurity Features
Endpoint-env includes a security manager to control access to sensitive data:
const endpoint = new EndpointEnv({
security: {
envAllowlist: ['API_KEY', 'BASE_URL'], // Only allow these env vars
envDenylist: ['SECRET', 'PASSWORD'], // Block specific env vars
maxDepth: 5, // Limit nested parameter depth
maxOutputSize: 1024 * 1024, // Limit output size (1MB)
allowCrypto: true, // Use crypto for random numbers
safeMode: true // Disable dangerous resolvers
}
});Parameters
Wrap a placeholder in curly braces and pass the value in at build time:
ENDPOINT_MATCH=https://api.example.com/matches/{matchId}endpoint.build('match', { matchId: 50 });
// https://api.example.com/matches/50Default values
{page:1}If no value is passed for page, it falls back to 1.
Nested parameters
{team.slug}endpoint.build('team', { team: { slug: 'man-utd' } });
// resolves team.slug from the nested objectFilters
Chain one or more filters onto a parameter with |:
{name|trim|lowercase}
{title|slug}
{name|replace(_, )}
{title|truncate(20)}
{path|prepend(/api/)}
{file|append(.json)}Built-in filters
Filter Description lowercase Convert to lowercase uppercase Convert to uppercase trim Trim whitespace capitalize Capitalize first letter slug Generate a URL-safe slug camel camelCase snake snake_case kebab kebab-case replace Replace text prepend Prefix a value append Suffix a value default Fallback when a value is empty truncate Limit string length padStart String.padStart padEnd String.padEnd slice Slice a string repeat Repeat a string urlencode URL-encode a value urldecode URL-decode a value number Coerce to a number, optionally fixed boolean Coerce truthy strings to a boolean split Split a string into an array join Join an array into a string first First element of an array last Last element of an array reverse Reverse a string or array
Expressions
Use ${...} for dynamic, parameter-free values:
${date}
${date(+5)}
${date(-2m)}
${timestamp}
${uuid}
${random(1,100)}
${env(API_KEY)}
${hostname}
${platform}
${arch}
${year}
${month}
${day}
${quarter}
${weekday}
${time}
${datetime}There's also a shorthand for pulling in another environment variable directly:
$HOST
$HOST:fallback.example.comBoth $ENV shorthand and ${env(...)} are inserted as-is, without URL-encoding, since they're typically trusted values like a hostname.
Registering your own filters and resolvers
endpoint.registerFilter('shout', value => String(value).toUpperCase() + '!');{value|shout}endpoint.registerResolver('season', () => '2026-2027');${season}Both can be async — use endpoint.buildAsync() instead of endpoint.build() when any registered filter or resolver returns a promise.
Plugins
Bundle related filters/resolvers together and install them as a unit:
endpoint.use(env => {
env.registerFilter('hello', value => 'Hello ' + value);
});Two plugins ship with the package:
const endpoint = require('endpoint-env');
endpoint.use(endpoint.builtinPlugins.date); // adds ${isoDate}, ${unix}
endpoint.use(endpoint.builtinPlugins.debug); // adds ${debug}, |json, |lengthDiscovering and validating endpoints
endpoint.list(); // ['match', 'fixtures', 'search', ...]
endpoint.has('match'); // true
endpoint.validate('match'); // true, or an error message
endpoint.validateAll(); // { match: true, fixtures: true, ... }
endpoint.preload(); // compiles every configured endpoint up frontValidation and build errors point directly at the problem in the template:
Unknown filter 'nope' on parameter 'name'.
https://api.com/{name|nope}
^^^^^^^^^^^Caching, freezing, and encoding
endpoint.clearCache();
endpoint.stats();endpoint.freeze(); // prevents any further registerFilter/registerResolver/use callsconst { create } = require('endpoint-env');
// Disable URL-encoding entirely
const raw = create({ encode: false });
// Or supply your own encoder
const custom = create({
encode(value) {
return value;
}
});Multiple instances
The default export is a ready-to-use singleton. For isolated configuration (a different prefix, cache size, or encoder), create your own instance:
const { create } = require('endpoint-env');
const endpoint = create({
prefix: 'MYAPP_',
cacheSize: 50
});Errors
All errors share a common base class and carry structured details:
const { errors } = require('endpoint-env');
try {
endpoint.build('match');
} catch (err) {
if (err instanceof errors.MissingParameterError) {
// handle missing parameter
}
}Available error types: EndpointEnvError, ParserError, ValidationError, MissingParameterError, FilterError, ResolverError, ConfigurationError, PluginError, LimitError.
CLI
endpoint-env list
endpoint-env validate
endpoint-env preload
endpoint-env stats
endpoint-env build match '{"matchId":12345}'API reference
build(scope, params?, context?) Build a URL synchronously
buildAsync(scope, params?, context?) Build a URL, awaiting async filters/resolvers
compile(scope) Parse and validate a template, returning its tokens
has(scope) Check whether a scope is configured
list() List all configured scope names
validate(scope) Validate a single scope
validateAll() Validate every configured scope
preload() Compile every configured scope up front
clearCache() Clear the compiled-template cache
stats() Inspect filters, resolvers, plugins, and cache state
freeze() Lock the instance against further registration
registerFilter(name, handler)
unregisterFilter(name)
registerResolver(name, handler)
unregisterResolver(name)
use(plugin) Install a plugin function
removePlugin(name)
config(options?) Load a .env file into process.env (standalone, like dotenv)
parse(source) Parse a .env-formatted string into an object
interpolate(value, env?) Interpolate variables in a stringLicense
MIT © Brytest
See the LICENSE file for details.
MIT License
Copyright (c) 2026 Brytest
Permission is hereby granted...
