What’s the best way to keep my database credentials secret in a Next.js + Express project?

clock icon

asked 386 days ago

message icon

2

eye icon

66

I’ve heard about dotenv. How do I load environment variables in both my Next.js frontend and Express backend so I don’t accidentally commit passwords?

2 Answers

Keeping your database credentials secret is paramount for application security. Using dotenv is an excellent approach for managing environment variables locally in both your Next.js and Express parts of the project, preventing you from accidentally committing sensitive information.

Here's how to implement it effectively:

1. The .env File

Create a file named .env.local in the root of your project (where package.json is located). This file will hold your sensitive environment variables.

1# .env.local
2DATABASE_URL=postgres://user:password@host:port/database
3API_SECRET_KEY=your_secure_api_key
4
5# For Next.js client-side variables, prefix with NEXT_PUBLIC_
6NEXT_PUBLIC_ANALYTICS_ID=UA-XXXXX-Y
1# .env.local
2DATABASE_URL=postgres://user:password@host:port/database
3API_SECRET_KEY=your_secure_api_key
4
5# For Next.js client-side variables, prefix with NEXT_PUBLIC_
6NEXT_PUBLIC_ANALYTICS_ID=UA-XXXXX-Y

Crucially, add .env.local (and other .env files like .env.development, .env.production) to your .gitignore file immediately.

1# .gitignore
2.env
3.env.*
1# .gitignore
2.env
3.env.*

2. Express Backend

For your Express backend, you'll typically install and use the dotenv package.

  1. Install dotenv:

    1npm install dotenv
    2# or
    3yarn add dotenv
    1npm install dotenv
    2# or
    3yarn add dotenv
  2. Load Environment Variables: In your main Express server file (e.g., server.js or app.ts), at the very top, import and configure dotenv.

    1// server.js
    2require('dotenv').config(); // Load environment variables from .env.local
    3
    4const express = require('express');
    5const app = express();
    6
    7const PORT = process.env.PORT || 3001;
    8const databaseUrl = process.env.DATABASE_URL;
    9const apiSecretKey = process.env.API_SECRET_KEY;
    10
    11// Use databaseUrl and apiSecretKey securely
    12console.log('Database URL (backend):', databaseUrl);
    13console.log('API Secret Key (backend):', apiSecretKey); // Do not log sensitive data in production!
    14
    15// Your Express routes and middleware go here
    16app.get('/api/data', (req, res) => {
    17 res.json({ message: 'Data from Express backend', secret: apiSecretKey });
    18});
    19
    20app.listen(PORT, () => {
    21 console.log(`Express server running on port ${PORT}`);
    22});
    1// server.js
    2require('dotenv').config(); // Load environment variables from .env.local
    3
    4const express = require('express');
    5const app = express();
    6
    7const PORT = process.env.PORT || 3001;
    8const databaseUrl = process.env.DATABASE_URL;
    9const apiSecretKey = process.env.API_SECRET_KEY;
    10
    11// Use databaseUrl and apiSecretKey securely
    12console.log('Database URL (backend):', databaseUrl);
    13console.log('API Secret Key (backend):', apiSecretKey); // Do not log sensitive data in production!
    14
    15// Your Express routes and middleware go here
    16app.get('/api/data', (req, res) => {
    17 res.json({ message: 'Data from Express backend', secret: apiSecretKey });
    18});
    19
    20app.listen(PORT, () => {
    21 console.log(`Express server running on port ${PORT}`);
    22});

    Express will now have access to process.env.DATABASE_URL and other variables defined in .env.local.

3. Next.js Frontend (and Server-side API Routes/Functions)

Next.js has built-in support for environment variables, so you generally don't need to install dotenv separately for the Next.js part of your project.

  1. Accessing Variables:

    • Server-side (API Routes, getServerSideProps, getStaticProps): Variables from .env.local are directly available via process.env.
    • Client-side (Browser): For variables to be exposed to the client-side code (browser), they must be prefixed with NEXT_PUBLIC_. This ensures Next.js bundles them into the client-side JavaScript.
    1// pages/index.js (or any Next.js page/component)
    2import Head from 'next/head';
    3
    4export default function Home({ databaseUrlServer, analyticsIdClient }) {
    5 return (
    6 <div>
    7 <Head>
    8 <title>Next.js Home</title>
    9 </Head>
    10 <main>
    11 <h1>Welcome to Next.js</h1>
    12 <p>Database URL (server-side only): {databaseUrlServer}</p>
    13 <p>Analytics ID (client-side): {analyticsIdClient}</p>
    14 {/* Example of client-side access */}
    15 <script>{`console.log('Client-side Analytics ID:', '${analyticsIdClient}');`}</script>
    16 </main>
    17 </div>
    18 );
    19}
    20
    21// This runs on the server
    22export async function getServerSideProps() {
    23 // Access DATABASE_URL (only available on the server)
    24 const databaseUrlServer = process.env.DATABASE_URL;
    25
    26 // Access NEXT_PUBLIC_ANALYTICS_ID (available on server and client)
    27 const analyticsIdClient = process.env.NEXT_PUBLIC_ANALYTICS_ID;
    28
    29 return {
    30 props: {
    31 databaseUrlServer: databaseUrlServer,
    32 analyticsIdClient: analyticsIdClient,
    33 },
    34 };
    35}
    1// pages/index.js (or any Next.js page/component)
    2import Head from 'next/head';
    3
    4export default function Home({ databaseUrlServer, analyticsIdClient }) {
    5 return (
    6 <div>
    7 <Head>
    8 <title>Next.js Home</title>
    9 </Head>
    10 <main>
    11 <h1>Welcome to Next.js</h1>
    12 <p>Database URL (server-side only): {databaseUrlServer}</p>
    13 <p>Analytics ID (client-side): {analyticsIdClient}</p>
    14 {/* Example of client-side access */}
    15 <script>{`console.log('Client-side Analytics ID:', '${analyticsIdClient}');`}</script>
    16 </main>
    17 </div>
    18 );
    19}
    20
    21// This runs on the server
    22export async function getServerSideProps() {
    23 // Access DATABASE_URL (only available on the server)
    24 const databaseUrlServer = process.env.DATABASE_URL;
    25
    26 // Access NEXT_PUBLIC_ANALYTICS_ID (available on server and client)
    27 const analyticsIdClient = process.env.NEXT_PUBLIC_ANALYTICS_ID;
    28
    29 return {
    30 props: {
    31 databaseUrlServer: databaseUrlServer,
    32 analyticsIdClient: analyticsIdClient,
    33 },
    34 };
    35}

Summary of Best Practices:

  • .env.local for Local Development: Use this file for all local environment variables.
  • .gitignore: Always add .env* to your .gitignore to prevent sensitive files from being committed to version control.
  • NEXT_PUBLIC_ Prefix: Only use NEXT_PUBLIC_ for variables that are safe to expose to the browser. Never prefix database credentials or API keys that should remain server-side.
  • Deployment: When deploying your application to platforms like Vercel, Netlify, Heroku, or AWS, you will set environment variables directly through their respective dashboards or CLI tools. This is the secure way to provide production credentials, as dotenv is primarily for local development.

Install:

1npm install dotenv
1npm install dotenv

Create a .env at project root:

1MONGODB_URI=mongodb://user:pass@host:port/db
2JWT_SECRET=supersecret
1MONGODB_URI=mongodb://user:pass@host:port/db
2JWT_SECRET=supersecret

In your code (both API and Express):

1// next.config.js (for Next.js)
2require('dotenv').config();
3module.exports = {
4 env: {
5 MONGODB_URI: process.env.MONGODB_URI,
6 JWT_SECRET: process.env.JWT_SECRET,
7 },
8};
1// next.config.js (for Next.js)
2require('dotenv').config();
3module.exports = {
4 env: {
5 MONGODB_URI: process.env.MONGODB_URI,
6 JWT_SECRET: process.env.JWT_SECRET,
7 },
8};
1// server.js (Express)
2require('dotenv').config();
3const express = require('express');
4const app = express();
5const dbUri = process.env.MONGODB_URI;
1// server.js (Express)
2require('dotenv').config();
3const express = require('express');
4const app = express();
5const dbUri = process.env.MONGODB_URI;
  • Never commit .env.
  • add entry of .env to .gitignore .
  • Access via process.env.VAR_NAME everywhere.

1

Write your answer here

Top Questions