Sling Academy
Home/Node.js/NodeJS: Declaring types when using dotenv with TypeScript

NodeJS: Declaring types when using dotenv with TypeScript

Last updated: February 06, 2024

Introduction

Combining Node.js with TypeScript provides a robust environment for building scalable and maintainable applications. However, working with environment variables in such a type-safe environment can be a bit challenging. This article aims to explore how to effectively use dotenv with TypeScript, specifically focusing on declaring types for environment variables to enhance code reliability and developer experience.

First, a brief overview: dotenv is a module that loads environment variables from a .env file into process.env. TypeScript, on the other hand, is a strong typing system for JavaScript. Combining these two requires some setup for seamless integration.

Getting Started

Ensure you have Node.js installed, and initialize your project if you haven’t:

npx create-react-app my-app --template typescript
cd my-app
npm i dotenv

This will set up a TypeScript project and install the required dotenv package.

Configure dotenv

To enable dotenv in your TypeScript application, you’ll need to import and configure it preferably at the entry point of your application:

import * as dotenv from 'dotenv';
dotenv.config();

This code snippet ensures that the dotenv configuration will read your .env file and add the variables to process.env.

Declaring Type for Environment Variables

The real challenge begins when dealing with the dynamic nature of process.env in a statically typed environment. TypeScript prefers knowing the type of every variable at compile time, but environment variables are read at runtime. This discrepancy leads us to the necessity of declaring explicit types for our environment variables.

The simplest way to achieve this is by defining an interface that describes the shape of your expected environment:

interface Env {
PORT: number;
DATABASE_URL: string;
}

However, to link this interface with process.env, we need some utility type magic to make process.env aware of our custom typing:

function getEnvVars(env: NodeJS.ProcessEnv): Env {
return {
PORT: parseInt(env.PORT, 10),
DATABASE_URL: env.DATABASE_URL,
};
}

This function takes the process.env object, asserts and returns an object matching our Env interface. Usage of such a function ensures that you are working with typed and validated environment variables across your application.

Type-safe Configuration Function

For better safety and reusability, wrap the above logic in a dedicated configuration module:

// config.ts
import * as dotenv from 'dotenv';

export interface EnvConfig {
port: number;
databaseUrl: string;
}

export function loadConfig(): EnvConfig {
dotenv.config();
return {
port: parseInt(process.env.PORT, 10),
databaseUrl: process.env.DATABASE_URL,
};
}

This module can now be imported wherever you need to use environment variables, providing type safety and making your code more robust and easier to understand.

Validation

To take this a step further, adding validation to your configuration is crucial for ensuring that your environment variables meet your application’s expected format and values:

// config.ts (continued)
...
if (!config.port || isNaN(config.port)) {
throw new Error('PORT must be a valid number.');
}
if (!config.databaseUrl) {
throw new Error('DATABASE_URL is required.');
}

Throwing errors for missing or invalid environment variables can prevent runtime errors and make debugging issues related to configuration much simpler.

Advanced Typings

For applications that require complex environment configurations, consider using advanced TypeScript features like Mapped Types or Conditional Types to create more flexible and maintainable environment type definitions.

See also:

Conclusion

Integrating dotenv and TypeScript by explicitly declaring types for environment variables significantly enhances type safety, aids in debugging, and improves developer experience. While it requires some setup, the benefits of having a fully typed configuration that can prevent common mistakes and ensure the integrity of your application make it well worth the effort.

Next Article: Node.js: How to programmatically run Git commands

Previous Article: Node.js: How to get location from IP address (3 approaches)

Series: Node.js Intermediate Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates
  • ExpressJS: How to pass variables to Pug templates