# Configuration Decoded: Lesser-Known Tips for Working with env-schema in Node.js — Liran Tal

Level up your Node.js apps with env-schema! Manage configurations effortlessly and learn useful practices for building for configuration management.

Canonical: https://lirantal.com/blog/configuration-decoded-lesser-known-tips-for-working-with-env-schema-in-nodejs

As Node.js developers, we often find ourselves dealing with various configuration options to tailor our applications for different environments. Configuration management is a critical aspect of building backend servers, allowing us to control the behavior of our applications without modifying the codebase, such as defining database configuration and other parameters.

In this blog post, we will explore the [env-schema](https://github.com/fastify/env-schema) npm package, an open-source library, part of the Fastify maintainers team, that simplifies configuration management in Node.js apps.

Whether you are a seasoned developer or just starting your Node.js journey, understanding how to manage configurations efficiently is essential for building scalable and maintainable applications.

## Introducing env-schema

The `env-schema` npm package offers a straightforward yet effective solution for handling configuration data in Node.js applications. It enables us to define a schema for our environment variables, ensuring that the required variables are present and have the correct data types. By validating and loading the configuration data, `env-schema` helps us avoid runtime errors and ensures our application starts with a valid configuration.

To get started, we need to install the package via npm:

```
npm install env-schema
```

Now, let’s dive into a code example that demonstrates how to use `env-schema` in a Node.js application with a schema defining essential configuration variables.

```
// config.js
const envSchema = require('env-schema');

const schema = {
  type: 'object',
  required: ['PORT', 'DB_USER', 'DB_PASS', 'DB_HOST', 'DB_PORT', 'DB_NAME'],
  properties: {
    PORT: {
      type: 'integer',
      default: 3000,
    },
    DB_USER: {
      type: 'string',
    },
    DB_PASS: {
      type: 'string',
    },
    DB_HOST: {
      type: 'string',
      default: 'localhost',
    },
    DB_PORT: {
      type: 'integer',
      default: 5432,
    },
    DB_NAME: {
      type: 'string',
    },
  },
};

const config = envSchema({ schema });

console.log('Configuration:', config);
```

In this example, we define a schema object containing the required environment variables (`PORT`, `DB_USER`, `DB_PASS`, `DB_HOST`, `DB_PORT`, and `DB_NAME`). Additionally, we provide default values for some variables to ensure our application works even if they are not explicitly set in the environment.

> WARNING: in a production environment you shouldn’t log the configuration due to privacy and security concerns. The use of `console.log` in the above code snippet is just an example for demonstration purposes.

When you run the application, `env-schema` will validate the environment variables based on the schema. If any required variables are missing or have incorrect types, it will throw an error with meaningful messages indicating which variables failed validation.

## env-schema Nuggets: unlocking hidden configuration features in Node.js

Now that we have a basic understanding of how to use `env-schema`, let’s explore other useful practices for managing configuration in Node.js apps.

### Data loading precedence in env-schema

One essential aspect of configuration management is determining the order of precedence for loading data from different sources. With `env-schema`, the order of precedence is as follows, from least significant to most:

1.  Data sourced from a `.env` file (when dotenv configuration option is set).
2.  Data sourced from environment variables in `process.env`.
3.  Data provided via the `data` configuration option.

As such, any configuration parameters provided via the `data` option will always take precedence and override any other values from either the `.env` file and process’s environment variables.

By understanding this precedence, we can ensure that our application’s configurations are correctly loaded.

### Supporting dual-parameters for the same configuration

In some scenarios, you may encounter applications that offer multiple ways to configure the same functionality. For example, instead of specifying individual database configuration variables (`DB_USER`, `DB_PASS`, `DB_HOST`, etc.), you might have a single variable called `DATABASE_URL` that contains all the necessary information. This approach is common, especially in platforms like Heroku.

To support both methods of configuration, we can use the `anyOf` schema declaration as part of our required fields. Let’s modify our previous schema to accommodate the `DATABASE_URL` option:

```
const schema = {
  type: 'object',
  required: ['PORT', 'HOST'],
  anyOf: [
    {
      required: ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'],
    },
    {
      required: ['DATABASE_URL'],
    },
  ],
};
```

With this updated schema, either specifying individual database variables or providing a `DATABASE_URL` will be considered valid, making your application more flexible in different environments.

## Custom validators in env-schema

When working with configuration management in Node.js using env-schema, most built-in types would work great out of the box. However, there are instances when your application requires more specialized validation for specific configuration variables. This is where custom validators come to the rescue.

Custom validators allow you to define your own validation logic, tailoring it precisely to your application’s needs. Let’s dive into how you can create and use custom validators with env-schema.

### Creating a custom validator

To create a custom validator, you need to provide a validation function that accepts a value and returns true if the value is valid or an error message as a string if it fails validation. The error message will be displayed when the configuration variable fails the validation check.

We are going to use Ajv custom validators for this and pass our own Ajv instance to env-schema. Create a `config.js` file with the following contents:

```
const { envSchema, str } = require("env-schema");
const Ajv = require("ajv");

function validateApiKey(schema, data) {
  if (!data || typeof data !== "string" || data.length !== 32) {
    validateApiKey.errors = [
      {
        keyword: "apiKeyValidation",
        message: "API key must be a non-empty string of length 32.",
      },
    ];
    return false;
  }
  return true;
}

// Create a new instance of Ajv
const ajv = new Ajv({ allErrors: true, removeAdditional: true });

// Add the custom validation keyword
ajv.addKeyword({
  keyword: "apiKeyValidation",
  validate: validateApiKey,
});

// Schema definition with custom validator
const schema = {
  type: "object",
  properties: {
    API_KEY: {
      default: "",
      apiKeyValidation: true, // Use the custom validation keyword
    },
  },
};

// Applying the schema to environment variables
const config = envSchema({ schema, dotenv: true, ajv });
console.log(config);
```

In this example, the `validateApiKey` function checks if the API key is a non-empty string and has a length of exactly 32 characters. If the validation fails, it throws a `ValidationError` with a descriptive error message. Otherwise, it returns true, indicating that the API key is valid.

Next, create a `.env` file in the same directory with an API\_KEY definition:

```
API_KEY=1234
```

And finally, run the config code to see the validation in action:

```
node config.js
```

An error should be thrown similar to the following, indicating that the API key is invalid:

```
/projects/env-schema/node_modules/env-schema/index.js:86
    throw error
    ^

  Error: env/API_KEY API key must be a non-empty string of length 32.

  errors: [
    {
      keyword: 'apiKeyValidation',
      message: 'API key must be a non-empty string of length 32.',
      instancePath: '/API_KEY',
      schemaPath: '#/properties/API_KEY/apiKeyValidation'
    }
  ]
```

By incorporating the custom validator into your schema, env-schema will automatically apply the validation function to the API\_KEY configuration variable during the validation process.

## Schema documentation with env-schema

There are times when configuration schemas can become complex, making it challenging for team members to understand the purpose and expected values of each configuration variable.

To address this issue, the schema configuration, supported by Ajv, provides two handy properties for adding documentation to your configuration schema: `examples` and `description`.

*   The `examples` property allows you to provide usage examples for each configuration variable in your schema. These examples showcase the format and expected values of the configuration, making it easier for developers to understand how to set up the environment variables correctly.

*   The `description` property enables you to include a brief description of the configuration variable’s purpose or usage. It acts as a quick summary that provides context and clarity to developers regarding the significance of each configuration setting.


We can build upon the above example to include these documentation properties for the `API_KEY` configuration variable:

```
// Schema definition with custom validator
const schema = {
  type: "object",
  properties: {
    API_KEY: {
      default: "",
      apiKeyValidation: true, // Use the custom validation keyword
      description: 'API key for accessing external services',
      examples: ['a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'],
    },
  },
};
```

## Conclusion

The `env-schema` npm package offers a fantastic solution for managing configuration in Node.js applications. By defining a schema for your environment variables, you can ensure that the correct configurations are in place for your app to run smoothly. Its validation and data loading precedence features give you confidence that your application starts with a valid and consistent configuration.

Happy coding and may your Node.js apps thrive with the power of env-schema!

## More writing

[All writing →](/blog)

*   Sep 5, 2026

    ### [Self-Hosting Hermes Agent with Matrix E2EE on Proxmox](/blog/self-host-hermes-agent-matrix-e2ee-proxmox)

*   Aug 2, 2026

    ### [The Practical Second Brain: Building an LLM-Operated Markdown Knowledge Graph](/blog/practical-second-brain-llm-markdown-knowledge-graph)

*   Aug 1, 2026

    ### [Building a Second Brain with LLMs and AI Agents](/blog/building-second-brain-llms-ai-agents)


* * *

Liran Tal is the author of the book [Node.js Secure Coding: Defending Against Command Injection Vulnerabilities](https://www.nodejs-security.com/)

[Start Building Secure Node.js Applications ↗](https://www.nodejs-security.com/)

 [![](/_astro/nodejs-secure-coding-book-3d-style.DiTWpzFs_Z3c7s1.png)](https://www.nodejs-security.com/)
