Contact Us

Reading .env Variables in Fastify with the fastify-env Plugin

I recently started learning the Fastify framework, which for some reason isn't particularly popular in the Russian-speaking segment of the internet. I have always stored configuration variables in a .env file. To read .env files in Express, I got used to the well-known dotenv library, whereas the Fastify ecosystem has its own library — @fastify-env.

I turned to the documentation... and understood nothing. I tried to implement what it described, but nothing worked. A YouTube tutorial from an Indian developer didn't help either (although everything worked out for the developer in the video)...

Blaming the late hour and a tired brain, I gave up and started looking for a ready-made solution online, and I found one fairly quickly. It turned out I wasn't the only one hitting this problem — it's a common pain point for developers coming to Fastify from Express.

What follows is a translation of this article from July 27, 2021 (updated February 18, 2022). Before reading it, I'd spend 5-10 minutes on the very short official documentation for the @fastify-env library.


Problem: how do I access my .env file in Fastify?

Based on my experience with Express, I was used to using the dotenv module to read configuration variables — database username and password, the secret for JSON web tokens, and others — from my .env file.

Having moved into the fastify plugin ecosystem, I installed fastify-env and tried to use it to load the contents of my .env file. The initial problem I ran into was that the documentation for accessing .env variables with fastify-env seemed a bit sparse, and I couldn't find a single good guide.

After trying several different approaches with fastify-env and failing to read the variables from .env, I gave up and installed dotenv. That approach worked, and I was able to connect fastify to a MongoDB database successfully. But the big drawback of using dotenv with fastify is that the .env variables aren't available on the fastify instance, so reaching them from modules buried deep in the directory structure quickly becomes a headache.

I planned to use jsonwebtokens in my application for server-side user authentication. To verify tokens, I needed to store the 'secret' on the server and reach it from different modules that contain the verification logic. The dotenv solution handled the database credentials fine, but it was too clunky for reaching the 'secret'. So I tried fastify-env again.

Solution, or the key points I missed

The first time I used fastify, I ran into several new concepts at once and missed a few important details in my early attempts to use fastify-env. I hope the short summary of what I learned below saves other fastify-env newcomers time and effort.

.env variables must be included in the schema

The first thing I missed on my first attempt to use fastify-env is that the variables in the .env file must be included in the schema used by fastify-env, otherwise they won't be available. The following snippets show how this works:

.env

USERNAME=databaseUsername
PASSWORD=doubleSecretDatabasePassword

server.js

const schema = {
  type: 'object',
  required: ['PASSWORD', 'USERNAME'],
  properties: {
    PASSWORD: {
      type: 'string'
    },
    USERNAME: {
      type: 'string'
    }
  }
}

Set the "data" key to "process.env"

The second key point I missed was that the data key in the options object must be set to "process.env" to read the .env file. Setting the dotenv key to true isn't enough. The following snippet shows how to set both keys correctly.

server.js

const options = {
  dotenv: true,
  data: process.env
}

The ready() function doesn't work the way I first thought

The third and last thing I didn't understand in my initial attempt to use fastify-env was that waiting on fastify.ready() before fastify.listen() doesn't load all the plugins in order. But waiting on fastify.after() on the line after fastify.register() guarantees that the .env variables are defined after fastify.after(), as the following snippet shows:

server.js

fastify.register(fastifyEnv, options)
await fastify.after()

// The .env variables are now defined

Let's put it all together!

The following snippet shows my full solution using fastify-env to configure the connection URL for authenticating against a MongoDB database, with the username and password values set in the .env file.

server.js

// Fastify
const fastify = require('fastify')({
  logger: true
})

const fastifyEnv = require('fastify-env')
const schema = {
  type: 'object',
  required: ['DB_PASSWORD', 'DB_USERNAME'],
  properties: {
    DB_PASSWORD: {
      type: 'string'
    },
    DB_USERNAME: {
      type: 'string'
    }
  }
}

const options = {
  confKey: 'config',
  schema,
  dotenv: true,
  data: process.env
}

const initialize = async () => {
  fastify.register(fastifyEnv, options)
  await fastify.after()

  // Database
  // Connection URL
  const username = encodeURIComponent(fastify.config.DB_USERNAME)
  const password = encodeURIComponent(fastify.config.DB_PASSWORD)
  const dbName = 'databaseName'

  const url = `mongodb://${username}:${password}@localhost:27017/${dbName}`

  fastify.register(require('./database-connector'), {
    url,
    useUnifiedTopology: true
  })
}

initialize()

// Start the server
(async () => {
  try {
    await fastify.ready()
    await fastify.listen(process.env.PORT)
  } catch (error) {
    fastify.log.error(error)
    process.exit(1)
  }
})()

I hope other developers find this useful. If any fastify-env experts have suggestions for improving this approach, please share them. Thanks for reading and happy coding!

"database-connection" is a fastify plugin I wrote to use the official MongoDB 4.x driver, because at the time fastify-mongodb used driver 3.x under the hood. Since then fastify-mongodb has been updated to use driver 4.x, so you should probably use it in your project.


That's it! Like the author of the article, I hope this piece saves beginner developers (which I am myself) time and nerves, since I struggled quite a bit trying to set all this up on my own.