Home > Enterprise >  Use dotenv in backend
Use dotenv in backend

Time:06-24

This is probably easier than I thought, but I´m kinda stuck using my .env file.

Folder structure simplified:

backend │ .env │ package.json
│ etc. └───src │ │ server.ts │ │ etc...

dotenv was installed via npm and imported with import 'dotenv/config';

Calledin server.ts:

// connect to DB
mongoose.connect(
  process.env.DB_CONNECTION,
  {
    useUnifiedTopology: true,
    useNewUrlParser: true,
    useCreateIndex: true,
  },
  () => {
    console.log('connected to db');
  }
);

returns:

string | undefined Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'.

CodePudding user response:

The reason you are encountering this is because the environment variable might not be set (there's no way for TypeScript to know this because it only works at compile time and not during runtime), so you need to narrow the environment variable value to string. You can do it manually, or use the built-in ok assertion function from Node:

import {ok} from 'assert/strict';

ok(
  process.env.DB_CONNECTION,
  'DB_CONNECTION environment variable is not defined',
);

// Now `process.env.DB_CONNECTION` is guaranteed not to be `undefined`

// connect to DB
mongoose.connect(
  process.env.DB_CONNECTION,
  {
    useUnifiedTopology: true,
    useNewUrlParser: true,
    useCreateIndex: true,
  },
  () => {
    console.log('connected to db');
  }
);

CodePudding user response:

The process.env property has the following typing

interface ProcessEnv {
  [key: string]: string | undefined;
}

Which means that typescript does not know at compile time which properties are defined and which properties are not. That's because .env files can be swapped after compilation so there's not really any way to know if they contain the correct properties.

One way to handle this is to wrap the env properties, so that you have type safe access to your props:

// envProps.ts
import 'dotenv/config'

function validateNonNull(value?: string) {
  if(value === undefined) throw new Error(".env not valid");
  return value;
}
class EnvProps {
  readonly DB_CONNECTION: string;
  readonly myOtherProp: string;
  readonly optionalProp?: string;
  constructor() {
    this.DB_CONNECTION = validateNonNull(process.env.DB_CONNECTION);
    this.myOtherProp = validateNonNull(process.env.myOtherProp);
    this.optionalProp = process.env.optionalProp;
  }
}

const envProps = new EnvProps();
export default envProps;

// index.ts
import envProps from "./envProps";

mongoose.connect(
  envProps.DB_CONNECTION,
  /* ... */
)

This approach ensures that the props are actually there, and fail early if the .env is not written correctly. It also helps having code autocompletion for the names of your variables, specially when your .env file starts to grow.

Another, simpler approach is, if you are 101% sure that your .env file or you env properties are correctly set, and that you didn't make a typo in your code, you can just assert that you know it's not null by using a non-null assertion.

mongoose.connect(
      process.env.DB_CONNECTION!,
      /* ... */
    )
  • Related