Environment variables store configuration that changes between environments (development, production, etc.).

Client vs Server Environment Variables

Client (browser):

  • Environment variables are embedded in our frontend code by bundlers like Vite
  • Anyone can see these values by inspecting our browser code
  • Only use for public configuration, like API URLs
  • Never put secrets here - they’re not secure!

Server:

  • Environment variables stay on the server
  • Can safely store sensitive data like API keys, database passwords
  • Only our server code can access them

Example of the difference:

// CLIENT-SIDE (insecure - anyone can see this)
const apiUrl = import.meta.env.VITE_API_URL; // Visible in browser
const apiKey = import.meta.env.VITE_API_KEY; // If the key is a secret do not use it like this, use it only on the backend

// SERVER-SIDE (secure - only server can access)
const apiKey = process.env.API_KEY; // Safe, stays on server

Setting Up Environment Variables for Our Express Server

The dotenv package automatically loads environment variables from a .env file into process.env when your application starts. This makes it easy to manage configuration without hardcoding values in your code.

Step 1: Install the dotenv package

npm install dotenv

Step 2: Create a .env file Create a file called .env in your project root (same level as package.json). BE SURE to add .env to .gitignore.

PORT=4000

Step 3: Configure dotenv in your code Update your src/index.ts file to load the environment variables:

import express from "express";
import dotenv from "dotenv";
dotenv.config(); // Load .env file

const app = express();
const PORT = process.env.PORT || 3000; // Use env var or default to 3000

app.get("/", (req, res) => {
  res.json({ message: "Hello world!" });
});

// other routes

app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

What changed?

  • import dotenv from 'dotenv'; - Imports the dotenv package
  • dotenv.config(); - Loads variables from the .env file into process.env
  • const PORT = process.env.PORT || 3000; - Uses the PORT from .env file, or defaults to 3000 if not set

Now your server will run on port 4000 (from the .env file) instead of 3000.

Note: Since you’re using tsx watch, the server automatically restarted when you saved the changes to your code, which picked up the new environment variables from the .env file. Your server will now be running on port 4000.

Development vs Production

  • Development: The .env file is used to load environment variables
  • Production: Environment variables are set in your hosting platform’s configuration (like Heroku, Vercel, Railway, etc.) and the .env file is ignored
  • This keeps your configuration flexible and secure across different environments

Example: Different environments use different values

# Development (.env file)
DATABASE_URL=postgresql://localhost:5432/dev_db
API_KEY=dev_key_123

# Production (hosting platform config)
DATABASE_URL=postgresql://prod-server:5432/prod_db
API_KEY=real_api_key_abc123

This way, your code stays the same but uses different databases, API keys, and other configuration based on the environment.



Repo link

Tags: