This document describes how the packages/shared module validates environment
variables at service startup, covering the two complementary utilities:
requireEnv and loadBaseConfig / loadIndexerConfig / loadFinalizationConfig.
All Vatix services validate their environment at boot time — not lazily at the point of first use. A missing or malformed variable causes an immediate, descriptive startup failure rather than a silent bug at runtime.
Two utilities work together:
| Utility | File | Purpose |
|---|---|---|
requireEnv() |
packages/shared/src/requireEnv.ts |
Fail-fast presence check |
loadBaseConfig() etc. |
packages/shared/src/config.ts |
Typed, validated config object |
A lightweight guard that asserts every listed variable is present and non-empty. Call it once at the top of a service entry point before any other initialization.
import { requireEnv } from '@vatix/shared';
requireEnv(['DATABASE_URL', 'API_KEY', 'REDIS_URL']);If any variable is missing the process exits immediately with code 1 and
prints exactly which keys are absent:
[requireEnv] Missing required environment variables:
- API_KEY
- REDIS_URL
The function accepts an optional second argument for testing without touching real environment state:
requireEnv(['DATABASE_URL'], { DATABASE_URL: 'postgresql://...' });config.ts exports three loader functions that read process.env, validate
every field, and return a strongly-typed config object. Services pass this
object around instead of accessing process.env directly.
Used by the API server and any service that shares the core stack.
import { loadBaseConfig } from '@vatix/shared';
const config = loadBaseConfig(); // reads process.envUsed by apps/indexer.
import { loadIndexerConfig } from '@vatix/shared';
const config = loadIndexerConfig();Used by apps/workers finalization worker.
import { loadFinalizationConfig } from '@vatix/shared';
const config = loadFinalizationConfig();All loaders accept an optional env parameter — a plain object — so they can
be called in unit tests without mutating process.env.
Each variable is validated according to its type. Invalid values throw a descriptive error that prevents startup.
Variables that must be present and non-empty. Missing value → startup failure.
| Variable | Used by |
|---|---|
DATABASE_URL |
All services |
STELLAR_RPC_URL |
All services |
ORACLE_SECRET_KEY |
API, Oracle |
API_KEY |
API |
ADMIN_TOKEN |
API |
Error example:
Missing required environment variable: API_KEY
Must be a valid URL and use one of the accepted schemes.
| Variable | Accepted schemes |
|---|---|
DATABASE_URL |
postgresql://, postgres:// |
REDIS_URL |
redis://, rediss:// |
STELLAR_RPC_URL |
https://, http:// |
Error example:
DATABASE_URL must use one of [postgresql:, postgres:], got: "mysql:"
Must be one of a fixed set of string values.
| Variable | Accepted values | Default |
|---|---|---|
NODE_ENV |
development | test | production |
development |
LOG_LEVEL |
debug | info | warn | error |
info |
ORACLE_LOG_LEVEL |
debug | info | warn | error |
info |
FINALIZATION_LOG_LEVEL |
debug | info | warn | error |
info |
INDEXER_LOG_LEVEL |
debug | info | warn | error |
info |
Error example:
NODE_ENV must be one of development | test | production, got: "staging"
Must be a positive integer, optionally within a bounded range.
| Variable | Min | Max | Default |
|---|---|---|---|
PORT |
1 | 65535 | 3000 |
BODY_LIMIT_BYTES |
1 | — | 65536 |
RATE_LIMIT_MAX |
1 | — | 100 |
RATE_LIMIT_HEAVY_MAX |
1 | — | 20 |
RATE_LIMIT_WRITE_MAX |
1 | — | 10 |
ORACLE_POLL_INTERVAL_MS |
5000 | 3600000 | 30000 |
ORACLE_CHALLENGE_WINDOW_SECONDS |
1 | — | 86400 |
FINALIZATION_INTERVAL_MS |
1000 | — | 60000 |
Error example:
PORT must be a positive integer, got: "abc"
PORT must be <= 65535, got: "99999"
These variables are safe to omit; a sensible default is used when absent.
| Variable | Default |
|---|---|
STELLAR_NETWORK |
testnet |
STELLAR_HORIZON_URL |
https://horizon-testnet.stellar.org |
REDIS_KEY_PREFIX |
vatix: |
INDEXER_CURSOR_KEY |
ingestion |
CORS_ALLOWED_ORIGINS |
http://localhost:3000,http://localhost:5173 (non-production) / empty (production) |
CORS_ALLOWED_ORIGINS is a comma-separated list of allowed browser origins.
CORS_ALLOWED_ORIGINS=https://app.vatix.io,https://staging.vatix.io
In production, if this variable is not set, no cross-origin requests are allowed. In development and test the local dev server origins are permitted by default.
The following variables are treated as secrets and are never logged in full, even at debug level:
DATABASE_URL(may contain password)REDIS_URL(may contain password)ORACLE_SECRET_KEYAPI_KEYADMIN_TOKEN
- Add it to
.env.examplewith a comment explaining purpose and whether it is required or optional. - Add the validation call in the appropriate loader in
packages/shared/src/config.tsusing the existing helpers (requireString,requirePositiveInt,loadUrl, etc.). - Add it to the relevant section of this document.
- If it is required at startup, add it to the
requireEnv()call in the service entry point.
All loaders accept an optional env parameter, making them testable without
touching process.env:
import { loadBaseConfig } from '@vatix/shared';
it('throws when DATABASE_URL is missing', () => {
expect(() =>
loadBaseConfig({
NODE_ENV: 'test',
STELLAR_RPC_URL: 'https://soroban-testnet.stellar.org',
// DATABASE_URL intentionally omitted
})
).toThrow('Missing required environment variable: DATABASE_URL');
});See packages/shared/src/config.ts for the full list of validation helpers.