You've probably come across files like .env, .env.local, .env.example etc. but what do these files actually store and why do we need them? Environment variables address the need to keep sensitive information, such as database URIs or API keys, private and accessible only to your application during runtime.
Handling environment variables requires caution because if it leaks to the wrong hands, it can spell disaster and that is why you'll often hear the universal warning "NEVER commit .env files to GitHub". As we will see in this post, not every value stored as env variables are kept secret from the public, sometimes your env variables can be read in plain text even if you didn't commit it to GitHub. I'll also share some best practices and common security pitfalls when dealing with environment variables.
What are Environment Variables?
Environment variables are key-value pairs that exist outside your code but are accessible by your application at runtime. They are created outside of your application but can be read by your application at runtime and are often used to store secrets.
So instead of hardcoding values like const apiKey = "my-secret-key" You can create the key-value pair inside your .env file
API_KEY="my-secret-key"And access the value anywhere in your code const apiKey = process.env.API_KEY;
It's common convention to write the keys in the .env file in all caps.
Backend environment variables are handled differently from frontend ones due to their separate execution environments: the frontend runs in the client's browser, while the backend runs on the server, hidden from the client.
How Environment Variables Work in the Backend
On the backend (Node.js, Python, PHP, etc.), environment variables are truly runtime values.
In Node.js, for example:
process.env.PORT
process.env.DB_URL
process.env.JWT_SECRETThese values can be read from; the operating system, .env file or hosting platforms (like Vercel, Render, Railway)
When working locally you'd mostly load your env variables from a .env file, to do that you'll need to install the dotenv package, store the variables in your .env file and load it in your app.
- Install
npm install dotenv - Create the .env file
PORT=5000
JWT_SECRET=e1ab5f5c1e1e44fcbcd307d989284b7837d5d53473c3f4f30eedade40d5ffbd3
DB_URL=mongodb://localhost:27017/app3. Load it in your app:
import dotenv from "dotenv";
dotenv.config();Finally you can access any of the variable using process.env.<VARIABLE_KEY>
Because backend code runs on the server, users cannot directly see your environment variables. This is why secrets like database passwords, third party service tokens (e.g OpenAI key) and JWT signing keys must always stay on the backend.
How Environment Variables Work in the Frontend
In the frontend (React, Vite, Next.js, etc.), environment variables are not runtime values in the same sense. They are injected at build time. This can be confusing to some developers but what it means is that;
- They are bundled into your JavaScript.
- Anyone can inspect them in the browser's developer console.
So they are not truly secret. It is almost the same as storing the value as a global variable in your code
Example in Vite
Stored in .env file or your hosting provider like (Vercel or Render)
VITE_API_URL=https://api.myapp.comAccessed in your code as const apiUrl = import.meta.env.VITE_API_URL
You do need to prepend VITE_ to all your env keys. It ensures that only explicitly exposed variables are included in the bundle.
Any environment variable in the frontend is public.
While storing an apiUrl this way is generally acceptable, sensitive information like third-party API keys (e.g., OpenAI) requires a more secure approach, which I will detail later.
To add to the first rule I mentioned in the introduction; NEVER PUT - Database passwords - JWT secrets - Private API keys - Stripe secret keys in frontend environment variables, they will be visible in the browser.
Why We Ignore .env Files in Git
You'll always want to include .env, .env.local in your .gitignore file because .env files contains secrets, if you accidentally commit and push them to GitHub they become part of your git history and remain so if you delete it later. Bots scan public repos for exposed secrets, this is how many developers accidentally leak API keys.
Instead you should commit .env.example which acts as a template of the actual .env file
PORT=
JWT_SECRET=
DB_URL=This tells other developers what variables they need, without exposing real values.
Security Concerns When Working With Environment Variables
1. Frontend Secrets Aren't Secrets
As mentioned earlier, anything shipped to the browser is public. Even if you "hide" it in a variable, users can open the DevTools, inspect network requests and read bundled code.
If you need to protect something, move it to the backend. For instance if you need to directly connect with OpenAI via your OpenAI API key, you'll do so by hitting a backend endpoint which will help you make the request to OpenAI and return the response to you. That way you can get CORS protection even if the user sees the backend endpoint you're hitting.
A Practical Example: Using the OpenAI API
Let's say you want to use the OpenAI API in your application. You might be tempted to do this in your frontend:
fetch("https://api.openai.com/v1/...", {
headers: {
Authorization: `Bearer ${import.meta.env.VITE_OPENAI_KEY}`
}
})This is a serious mistake.
Even if the key is stored in an environment variable, it gets injected into your JavaScript bundle during build time. Anyone can open DevTools, inspect the request, and copy your API key.
Once exposed, that key can be abused, potentially costing you money or compromising your system.
The Correct Approach: Use the Backend as a Proxy
Instead of calling OpenAI directly from the frontend, you should:
- Send a request from your frontend to your backend.
- Let your backend attach the OpenAI API key and forward the request to OpenAI.
- Return the response to the frontend.

In this setup:
- The API key lives only on the server and the browser never sees it.
- Even if users inspect the network tab, they only see your backend endpoint (e.g.,
/api/generate), not your OpenAI key.
Other benefits of using this approach:
- You gain control over CORS and can restrict which origins can call your backend..
- You can add authentication, rate limiting, and logging.
Even if a user sees your backend endpoint in the network tab, they still cannot access the secret key. And with proper backend protection (authentication, rate limits), you can prevent abuse.
2. Exposing Secrets in Logs
Be careful with:
console.log(process.env);In production, logs may be stored in logging services. Accidentally logging secrets can create vulnerabilities.
3. Git History Leaks
Even if you delete a committed .env file, it still exists in Git history.
The easiest way to fix this is the rotate or change the compromised keys.
Prevention is better than cleanup so always remember to include .env, .env.local in your .gitignore file before storing secrets in .env file
4. Misconfigured Hosting Platforms
When deploying, add secrets in the platform dashboard instead of relying on local .env files in production. Also, double-check environment-specific variables for development and production.
Best Practices Summary
- NEVER store secrets in frontend environment variables.
- ALWAYS ignore
.envfiles in Git, commit a.env.exampletemplate instead. - Rotate keys immediately if leaked.
- Treat environment variables as configuration, not business logic.
Conclusion
In conclusion, environment variables are essential for managing configuration and sensitive information in both backend and frontend applications. However, they require careful handling to avoid security vulnerabilities.
Backend environment variables are runtime values that can securely store secrets, while frontend environment variables are injected at build time and are inherently public. Therefore, sensitive information like API keys, database passwords, and JWT secrets should never be stored in frontend environment variables. Instead, a backend proxy should be used to protect these secrets.
It's crucial to include .env files in .gitignore to prevent accidental commits to Git repositories and to rotate keys immediately if leaked. Additionally, avoid exposing secrets in logs and ensure that hosting platforms are properly configured to manage environment variables securely. By following these best practices, you can effectively utilize environment variables while minimizing the risk of exposing sensitive information.