{"id":16528669,"url":"https://github.com/zetlen/camelspace","last_synced_at":"2026-05-19T02:08:49.162Z","repository":{"id":51138462,"uuid":"179133526","full_name":"zetlen/camelspace","owner":"zetlen","description":"Transform flat ENVIRONMENT_VARIABLES into { namespaced: { objects } } and vice versa.","archived":false,"fork":false,"pushed_at":"2023-03-04T03:32:59.000Z","size":514,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-03-21T18:50:50.531Z","etag":null,"topics":["camelcase","configuration","environment-variables","namespace","nodejs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zetlen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-04-02T18:12:23.000Z","updated_at":"2021-05-25T13:48:03.000Z","dependencies_parsed_at":"2023-10-12T06:15:07.072Z","dependency_job_id":null,"html_url":"https://github.com/zetlen/camelspace","commit_stats":{"total_commits":25,"total_committers":3,"mean_commits":8.333333333333334,"dds":"0.19999999999999996","last_synced_commit":"6f97bc3b37c4d40e85995b84f348092a440a598e"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zetlen%2Fcamelspace","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zetlen%2Fcamelspace/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zetlen%2Fcamelspace/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zetlen%2Fcamelspace/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zetlen","download_url":"https://codeload.github.com/zetlen/camelspace/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247247489,"owners_count":20907974,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["camelcase","configuration","environment-variables","namespace","nodejs"],"created_at":"2024-10-11T17:41:10.539Z","updated_at":"2026-05-19T02:08:49.120Z","avatar_url":"https://github.com/zetlen.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Helper library for using environment variables fluently and readably, so you can\nuse them for your app settings instead of nonstandardized config files.\n\nGet neat groups of camelCased environment variables from a large, flat,\nSCREAMING_SNAKE_CASED NodeJS `process.env` object.\n\n![camelspace](https://user-images.githubusercontent.com/1643758/55430521-f73b0e80-5553-11e9-8aed-3c74e33f5a50.jpg)\n\nIf: \n```sh\n# .env\nBLUE_API_KEY=12345\nBLUE_API_SECRET=abcdefghijklm\nBLUE_ID=foo\nRED_API_KEY=09876\nRED_API_SECRET=zyxwvutsrqpon\nRED_LOG_LEVEL=debug\n```\n\nThen instead of:\n\n```js\nblueClient({\n  key: process.env.BLUE_API_KEY\n  apiSecret: process.env.BLUE_API_SECRET,\n  id: process.env.BLUE_ID\n});\n\nredClient({\n  key: process.env.RED_API_KEY\n  apiSecret: process.env.RED_API_SECRET,\n  logLevel: process.env.RED_LOG_LEVEL\n});\n```\n\nyou can do\n\n```js\nconst env = camelspace.of(['blue', 'red'], process.env);\nblueClient(env.blue);\nredClient(env.red);\n```\n\nAnd it handles arbitrary levels of nesting and what have you. OK, here's the long version.\n\n# Contents\n\n1. [Usage](#usage)\n   1. [Managing Groups of App Settings with `camelspace.of()`](#managing-groups-of-app-settings-with-camelspaceof)\n1. [Advanced Usage](#advanced-usage)\n   1. [Scoped environment objects](#scoped-environment-objects)\n   1. [Reversing the transform](#reversing-the-transform)\n   1. [Unscoped transforming](#unscoped-transforming)\n1. [API Reference](#api-reference)\n   1. [`camelspace.of()`](#camelspaceof)\n   1. [`camelspace(namespace)`](#camelspacenamespace)\n      1. [`configFactory.fromEnv(env)`](#configfactoryfromenvenv)\n      1. [`configFactory.toEnv(configObj)`](#configfactorytoenvconfigobj)\n      1. [`configFactory.for(\u003csection\u003e, \u003csubsections\u003e, [env])`](#configfactoryforsection-subsections-env)\n   1. [FAQ](#faq)\n      1. [Why not just a JSON configuration file?](#why-not-just-a-json-configuration-file)\n      1. [What are valid environment variable names?](#what-are-valid-environment-variable-names)\n      1. [How does Camelspace validate?](#how-does-camelspace-validate)\n\n# Usage\n\n```sh\nnpm install camelspace\n```\n\n```js\nimport camelspace from 'camelspace';\n```\n\nCall `camelspace` with some strings to show it how your app settings are stored.\nIt will return a sanitized, readable, camel-cased object that helps you keep\nyour environment variables organized. There are three modes of usage:\n\n- [`camelspace.of(namespaces)`](#camelspaceof) for splitting your enviroment into simple, named groups\n- [`camelspace(namespace)`](#camelspace) for nested configurations based on a single root namespace\n\n## Managing Groups of App Settings with `camelspace.of()`\n\nA common environment need in a modern app is to juggle more than one credential for more than one external API. That's a lot of things called \"API KEY\"! The best solution is to namespace, and camelspace makes that easy.\n\nLet's say you want to integrate with both Twitter and Slack. [The Twitter API client tells you it needs four environment variables to start up.](https://github.com/FeedHive/twitter-api-client/blob/d337cb870f5803aa419673c7ab0b99e9d48e8b7d/README.md#usage)\n\n```js\nconst twitterClient = new TwitterClient({\n  apiKey: '\u003cYOUR-TWITTER-API-KEY\u003e',\n  apiSecret: '\u003cYOUR-TWITTER-API-SECRET\u003e',\n  accessToken: '\u003cYOUR-TWITTER-ACCESS-TOKEN\u003e',\n  accessTokenSecret: '\u003cYOUR-TWITTER-ACCESS-TOKEN-SECRET\u003e',\n});\n```\n\nMeanwhile, Slack has its own credentials:\n\n```js\nconst slackApp = new App({\n  token: '\u003cYOUR-SLACK-TOKEN\u003e'\n  signingSecret: '\u003cSLACK-SIGNING-SECRET\u003e'\n});\n```\n\nYou need environment variables for each of these. And you want to distinguish between similarly named credentials, so you do:\n\n```sh\n# .env`\nTWITTER_CONSUMER_KEY='\u003cYOUR-TWITTER-API-KEY\u003e'\nTWITTER_CONSUMER_SECRET='\u003cYOUR-TWITTER-API-SECRET\u003e'\nTWITTER_ACCESS_TOKEN='\u003cYOUR-TWITTEER-ACCESS-TOKEN\u003e'\nTWITTER_ACCESS_TOKEN_SECRET='\u003cYOUR-TWITTER-ACCESS-TOKEN-SECRET\u003e'\n\nSLACK_TOKEN='\u003cYOUR-SLACK-TOKEN\u003e'\nSLACK_SIGNING_SECRET='\u003cSLACK-SIGNING-SECRET\u003e'\n```\n\nAnd now, once you've pulled `.env` into your Node environment (using\n[dotenv](https://npmjs.com/package/dotenv) perhaps?) you can create a client:\n\n```js\n// Plug the environment into the clients.\nconst twitterClient = new TwitterClient({\n  apiKey: process.env.TWITTER_CONSUMER_KEY,\n  apiSecret: process.env.TWITTER_CONSUMER_SECRET,\n  accessToken: process.env.TWITTER_ACCESS_TOKEN,\n  accessTokenSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET\n});\nconst slackApp = new App({\n  token: '\u003cYOUR-SLACK-TOKEN\u003e'\n  signingSecret: '\u003cSLACK-SIGNING-SECRET\u003e'\n});\n```\n\nThis is verbose and error-prone.\nCamelspace takes advantage of the capitalization patterns of JavaScript\nvariables and environment variables, and automates some of this for you.\n\n```js\n// Get a camelcased object of all env vars beginning with `TWITTER_`.\nconst { twitter } = camelspace.of(['twitter']);\n// Use it for the config instead.\nconst twitterClient = new TwitterClient({\n  apiKey: twitter.apiKey,\n  apiSecret: twitter.apiSecret,\n  accessToken: twitter.accessToken,\n  accessTokenSecret: twitter.accessTokenSecret\n});\n// Get a camelcased object of all env vars beginning with `SLACK_`.\nconst { slack } = camelspace.of(['slack']);\n// Use it for the config instead.\nconst slackApp = new App({\n  token: slack.token,\n  signingSecret: slack.signingSecret\n});\n```\n\nNote that because we named our environment variables carefully so their\ncamelcased versions matched up with the constructor signatures, it can actually\nget even simpler.\n\nWhich is when you really start to see the benefit:\n\n```js\n// Get a camelcased object of every section of environment relevant to your app.\nconst config = camelspace.of(['twitter', 'slack']);\nconst twitterClient = new TwitterClient(config.twitter);\nconst slackApp = new App(config.slack);\n```\n\n:warning: Note that **camelspace does no type coercion or validation**; that's\nfor other libraries to do. In particular, **camelsace does not validate\nenvironment variables against your app's own configuration schema.** Definitely\nuse utilities like [envalid](https://npmjs.com/package/envalid) to validate\ntheir types, docstrings, and defaults.\n\nIf you have more complex needs, such as nested configuration or multiple versions of the same app, then read on...\n\n# Advanced Usage\n\nThe `camelspace()` function creates _config factory objects_, which can be\nreused with different `env` objects, or recursively called to create more\nspecific configurators within a namespace.\n\nA config factory object has methods: `.fromEnv()`, `.toEnv()` and `.for()`.\n\nCall `configFactory.for` with a **root namespace**, a list of **configuration\nsections**, and optionally an **environment object**. If you pass no third\nargument, `configFactory.for` will use `process.env` as the environment object.\n\n```js\nconst getAppConf = camelspace('myApp');\nconst [{ mode }] = getAppConf.for('indexer', ['cache']);\n// This retrieves process.env.MY_APP_INDEXER_CACHE_MODE in a different style.\n\nif (mode === 'redis') {\n  // etc\n}\n```\n\nThis is more verbose than the fluent style, but it can aid in testability.\n\n:information_source: _The following examples all use an environment generated from the [example environment variables from the FAQ](#what-are-valid-environment-variable-names)._\n\n## Scoped environment objects\n\nThe `.fromEnv()` and `.toEnv()` methods return scoped objects\nconstrained to the root namespace of the factory.\n\n```js\nconst appConfig = camelspace('myApp'); // could be \"MY_APP\";\nconst appEnv = appConfig.fromEnv(process.env);\nappEnv.coreMode === 'test';\nappEnv.coreToken === 'ba6bd9a8e6da';\nappEnv.ciToken === '1730eb9867d';\n\nconst coreConfig = appConfig('core'); // could be \"CORE\";\nconst coreEnv = coreConfig.fromEnv(process.env);\ncore.mode === 'test';\ncore.token === 'ba6bd9a8e6da';\n\n// You could get the equivalent with the following, but that requires\n// more modules to know the parent namespace, which is tigher coupling.\nconst coreConfig = camelspace('myAppCore');\nconst coreEnv = coreConfig.fromEnv(process.env);\ncore.mode === 'test';\ncore.token === 'ba6bd9a8e6da';\n```\n\nTransformers can compose arbitrarily deep.\n\n```js\nconst telemetryLogEnv = camelspace('myApp')('telemetry')('log').fromEnv(\n  process.env\n);\ntelemetryLogEnv.enabled === '1'; // Note that camelspace does no type coercion.\ntelemetryLogEnv.level === 'debug';\n```\n\n## Reversing the transform\n\nTurn a camelCased object back into an object of SCREAMING_SNAKE_CASE environment\nvariables by using `configFactory.toEnv(obj)`. You might need this to pass\nenvironment variables to a child process, for example.\n\nA transform function has a method `.toEnv(camelSpacedObject)`, which does the\nreverse operation `transformer.toEnv(object)` transforms any object returned\nby the same transformer into a flat `SCREAMING_SNAKE_CASED` object with the\noriginal prefix. For any transformer, `.fromEnv()` and `.toEnv()` are inverse\noperations (barring the original `.fromEnv(process.env)`, which elides variables\noutside the approved pattern and/or the given namespace.)\n\n```js\nconst appConfig = camelSpace('myApp');\nconst appEnv = appConfig.fromEnv(process.env);\n/** appEnv looks like:\n * {\n *   coreMode: \"test\",\n *   coreToken: \"ba6bd9a8e6da\",\n *   ciToken: \"1730eb9867d\"\n *   telemetryApiEndpoint: \"https://example.com\"\n *   telemetryLogEnabled: \"1\",\n *   telemetryLogLevel: \"debug\"\n * }\n */\nconst originalEnv = appConfig.toEnv(appEnv);\n/** originalEnv looks like:\n * {\n *   MY_APP_CORE_MODE: \"test\",\n *   MY_APP_CORE_TOKEN: \"ba6bd9a8e6da\",\n *   MY_APP_CI_TOKEN: \"1730eb9867d\"\n *   MY_APP_TELEMETRY_API_ENDPOINT: \"https://example.com\"\n *   MY_APP_TELEMETRY_LOG_ENABLED: \"1\",\n *   MY_APP_TELEMETRY_LOG_LEVEL: \"debug\"\n * }\n */\n```\n\n## Unscoped transforming\n\nThe `camelspace` default export is just a config factory whose namespace is the empty string `''`.\n\nCall `camelspace.fromEnv()` with a `process.env` object (or any object with\n`SCREAMING_SNAKE_CASE` properties). This returns an object representing the\nwhole environment, with all properties changes to `camelCase`. _(No\nvalidation or type coercion is done on the values of the object; `camelspace`\nonly formats the object keys.)_\n\n```js\nconst env = configFactory.fromEnv(process.env)\nenv.myAppCoreMode === 'test';\nenv.thirdPartyNullableBoolean === '';\nenv.port === undefined\n```\n\nThe corresponding `camelspace.toEnv(env)` will turn a config object back into\nenvironment variables, not limited by a scope prefix.\n\n# API Reference\n\n## `camelspace.of()`\n\n| Parameter | Description |\n| --------- | ----------- |\n| `namespaces` | Array of camelCased prefixes for each section of env vars to collect. For instance, `['twitter', 'googleAnalytics']` would get all env vars starting with `TWITTER_`, and all env vars starting with `GOOGLE_ANALYTICS`. |\n| `env` | _Optional, defaults to `process.env`_. If passed, camelcase will use this argument as the object of env vars to process, instead of `process.env`. |\n\nReturns an object with keys matching the list of `namespaces`, and values matching camel cased versions of the env vars under each namespace.\n\n**Example:**\n```js\nconsole.log(\n  camelspace.of(\n    ['twitter', 'googleAnalytics'],\n    {\n      GOOGLE_ANALYTICS_ACCOUNT_ID: 123456,\n      TWITTER_USER: '@dril',\n      TWITTER_API_KEY: 'abcdef',\n      UNRELATED: 'foo'\n    }\n  )\n);\n```\n```\n{\n  twitter: {\n    user: '@dril',\n    apiKey: 'abcdef'\n  },\n  googleAnalytics: {\n    accountId: 123456\n  }\n}\n```\n\nConsidering the underscores in env var names to be \"levels\", you can split out\nyour vars however you wish. For instance, camelspace will turn the same\nenvironment into a different object if `google` is used instead:\n\n```\n{\n  twitter: {\n    user: '@dril',\n    apiKey: 'abcdef'\n  },\n  google: {\n    analyticsAccountId: 123456\n  }\n}\n```\n\nFor more complex objects with deeper nesting, use the `camelspace()` factory.\n\n## `camelspace(namespace)`\n\n| Parameter   | Description                                                                                                                                                                                                                                             |\n| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `namespace` | The camelcased prefix for all the env vars you want to use. For instance, `myAppCore` would limit to all varnames beginning with `MY_APP_CORE_`.  |\n\nReturns a **config factory object** with `fromEnv`, `toEnv`, and `for` methods. The object is also\na callable function that can return another config factory, with a deeper scope.\n\n### `configFactory.fromEnv(env)`\n\n| Parameter | Description |\n| --------- | ----------- |\n| `envObj` | Object with SCREAMING_SNAKE_CASE keys, to use as the source of environment variables.\n\nReturns an object of the variables whose names begin with the config factory's\nnamespace, and the values camelCased.\n\n\n### `configFactory.toEnv(configObj)`\n\n| Parameter | Description |\n| --------- | ----------- |\n| `configObj` | Object with camelCased keys, to use as the source of environment variables.\n\nReturns an object with SCREAMING_SNAKE_CASE keys, whose properties all begin with the SCREAMING_SNAKE_CASE transofmration of the config factory's namespace.\n\n\n\n### `configFactory.for(\u003csection\u003e, \u003csubsections\u003e, [env])`\n\n| Parameter   | Description                                                                                                                                                                                                                                             |\n| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `namespace` | The camelcased prefix for all the env vars you want to use. For instance, `myAppCore` would limit to all varnames beginning with `MY_APP_CORE_`.                                                                                                        |\n| `sections`  | An array of strings, with each string representing a camelcased sub-namespace with the `namespace`. For instance, `['network']` would return a length-1 array whose first index was an object of all the env vars starting with `MY_APP_CORE_NETWORK_`. |\n| `env`       | _Optional, defaults to `process.env`_. If passed, camelspace will use this object to lookup env vars, instead of the Node builtin `process.env`.\n\nReturns an array of objects, of the same length as the **sections** argument.\nEach section is an object whose keys are camelcased environment variable keys\nwith the namespace prefix removed, and whose values are the values of the\nenvironment variables in the object. No coercion is done; the values are\nexactly what exists in `process.env`, or whatever argument object was sent as\nthe third argument.\n\n## FAQ\n\n### Why not just a JSON configuration file?\nWe're supposed to configure well-designed apps with [environment variables](https://12factor.net/config), because they are simple, cross-platform, easy to combine and override, and separate from code. Using a JSON configuration file or an `.rc` configuration file invites a few antipatterns to come in and roost:\n\n- File-based config implies inheritance-based config: a directory has an .rc file, which overrides the whole project's .rc file, which extends your home directory's .rc file. This works great for developer tools, but is _terrible_ for deployed software, because it makes runtime behavior highly dependent on the state of the filesystem. It can take a lot of debugging to realize that what's breaking your app is an .rc file in `/usr/share` or something else far away from your code.\n- Eventually JSON config files become JS config files, and JS config files become basically scripts which build config objects, and then your config isn't declarative anymore.\n\n\n### What are valid environment variable names?\n\nEnv vars should work everywhere, but truly cross-platform environments have some constraints.\n\n```sh\n# It's a flat dictionary with no namespacing or hierarchy.\nMY_APP_CORE_MODE=test\nMY_APP_CORE_TOKEN=ba6bd9a8e6da\nMY_APP_CI_TOKEN=1730eb9867d\nMY_APP_INDEXER_CACHE_MODE=redis\nTHIRD_PARTY_VAR='Who knows!'\n\n# You can do ad-hoc namespacing, but is often ambiguous;\nMY_APP_NET_SERVICES_REDIS_HOST=redis.local\nMY_APP_NET_RETRIES=3\n\n# All values are strings. How do you escape or validate?\nTHIRD_PARTY_NULLABLE_BOOLEAN=\nHOST='a.url...maybe?!'\nport=655E9🐘\n\n# And they should be SCREAMING_SNAKE_CASE!\nMY_APP_TELEMETRY_API_ENDPOINT=https://example.com\nMY_APP_TELEMETRY_LOG_ENABLED=1\nMY_APP_TELEMETRY_LOG_LEVEL=debug\n```\n\nSome operating systems may allow more flexible environment variables, but not all of them, and the point of using them is to be maximally portable. Escaping rules differ; shell syntax differs; some shells aren't case sensitive, and more. [The Open Group](http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html) defines some restrictions here, but the easiest rule to remember is **ONLY_CAPITAL_ASCII_LETTERS_AND_UNDERSCORES_NO_FUNNY_BUSINESS**.\n\n### How does Camelspace validate?\n\n:warning: To pursue this ideal, `camelspace` will ignore any environment variables that don't match this format:\n\n- First character **must** be `[A-Z]`\n- Subsequent characters may be `[A-Z]`, `[0-9]`, or `_`\n\nAt the very least, to ensure cross-platform consistency, the incoming environment variables need to be formatted in this manner.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzetlen%2Fcamelspace","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzetlen%2Fcamelspace","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzetlen%2Fcamelspace/lists"}