Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 51

Eliminating Hardcoded Secrets and Configuration Values

Recommended resources

Introduction

In the previous lesson, you successfully deployed your application and verified that both the live API and its Swagger documentation are accessible online. This is a huge milestone.

With the application running, we now turn to the final but crucial pre-submission cleanup. This lesson focuses on a core professional practice: refactoring your project to eliminate any hardcoded secrets or configuration values. Storing sensitive information like database credentials directly in your code is a major security risk and makes your application inflexible. By the end of this lesson, you will have a secure, configurable, and production-ready codebase.


The Problem with Hardcoded Secrets

Throughout development, it's common to use hardcoded values for things like the server port or database connection string. For instance, you likely have code similar to this:

// Hardcoded port
const PORT = 3000;
app.listen(PORT, ...);

// Hardcoded and exposed database credentials
mongoose.connect('mongodb+srv://myuser:mypassword@cluster.xyz.mongodb.net/test');

This presents two major problems:

  1. Security Risk: If you push this code to a public GitHub repository, your database username and password will be exposed to the world.
  2. Inflexibility: When you deploy your app, the hosting service (like Render) might assign a different port. A hardcoded port will cause your application to fail on startup. Different environments (development, testing, production) often require different configurations.

The Solution: Environment Variables and dotenv

The industry-standard solution is to store configuration in environment variables. These are variables that exist outside your application's code, in the environment where the code is run. In Node.js, they are accessible via the global process.env object.

For local development, we use a fantastic package called dotenv. It loads variables from a special file named .env directly into process.env, simulating a real production environment.

The following video gives a great overview of this process.

3.4 Hiding API Keys with Environment Variables (dotenv) and Pushing Code to GitHub

This video from The Coding Train provides a clear, practical walkthrough of why we need to hide secrets and how to use the dotenv package to do it. Focus on the three key files involved: your server file, the .env file, and .gitignore.

Watch from the beginning to 04:02 to understand the core concept, and then from 05:26 to 06:47 to see how the .gitignore file is crucial for security. The video covers creating the .env file, configuring dotenv, using process.env, and updating .gitignore.


Your Refactoring Checklist: A Step-by-Step Guide

Let's apply these concepts directly to your project. Follow this checklist to systematically remove all hardcoded values.

Step 1: Install dotenv

In your project's root directory, open the terminal and run the following command to add dotenv as a dependency:

npm install dotenv

Step 2: Create the .env File

In the same root directory, create a new file named exactly .env. This file will hold your secrets. Add your PORT and MONGO_URI to it.

Important: The syntax is KEY=VALUE. Do not use quotes and ensure there are no spaces around the = symbol.

.env

PORT=3000
MONGO_URI=mongodb+srv://<your_username>:<your_password>@cluster0.xxxxx.mongodb.net/myDatabase?retryWrites=true&w=majority
  • Replace the MONGO_URI value with your actual MongoDB connection string.

Your project should now include a .env file, similar to the example shown here:

Project Structure and Environment Variables (.env)
An example `.env` file in a code editor, showing key-value pairs for application configuration. Your file will contain your specific `PORT` and `MONGO_URI`.

Step 3: Secure Your Secrets with .gitignore

This is the most critical step for security. You must prevent your .env file from ever being committed to Git.

Open your .gitignore file and add .env on a new line. It should look like this:

.gitignore




# Dependencies
node_modules




# Environment variables
.env

This instruction tells Git to ignore the .env file, keeping your secrets safe on your local machine.

Step 4: Load the Environment Variables

Now, you need to tell your application to load the variables from the .env file. Open your main application file (e.g., app.js or server.js) and add the following line at the very top:

// This line MUST be at the top of your file
require('dotenv').config();

const express = require('express');
// ... rest of your application setup

Placing this line first ensures that all environment variables are loaded and available before any other part of your code needs them.

Step 5: Refactor Your Code to Use process.env

Finally, replace the hardcoded values in your application with references to process.env.

  1. Server Port: Find your app.listen call and modify it. We provide a fallback value (3000) in case process.env.PORT isn't defined. This is a common and robust practice.

    • Before:
      const PORT = 3000;
      app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}`);
      });
      
    • After:
      const PORT = process.env.PORT || 3000;
      app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}`);
      });
      
  2. MongoDB Connection: Find your mongoose.connect call and update it to use the MONGO_URI from your environment.

    • Before:
      mongoose.connect('mongodb+srv://user:pass@cluster.mongodb.net/db', { /* options */ });
      
    • After:
      mongoose.connect(process.env.MONGO_URI, { /* options */ });
      

After these changes, your application is no longer tied to specific configuration values in the code.

Best Practice: Create a .env.example File

Since your .env file is ignored by Git, how will another developer (or your future self) know which environment variables are needed to run the project? The standard practice is to create a .env.example file.

  1. Copy your .env file and rename it to .env.example.
  2. Remove the secret values, leaving only the keys.

.env.example

PORT=3000
MONGO_URI=

This file is committed to your repository and serves as a template for anyone setting up the project.


Conclusion

Congratulations! You have successfully refactored your application to securely manage configuration and secrets. This is a non-negotiable skill for any backend developer and a key requirement for a professional-grade project.

Key Takeaways:

  • Never hardcode secrets like API keys or database strings in your code.
  • Use a .env file to store these values during local development.
  • Always add .env to your .gitignore file to prevent exposing secrets in version control.
  • Use the dotenv package and call require('dotenv').config() at the top of your application to load the variables.
  • Replace hardcoded values in your code with process.env.VARIABLE_NAME.
  • Provide a .env.example file as a template for other developers.

Next Up:

With your code now secure and configurable, we'll move to the final checks before submission. In the next lesson, you will perform a final review of all API endpoints to ensure they return the correct HTTP status codes for both success and error cases. This final polish ensures your API behaves predictably and professionally.