Skip to main content

🔑 Environment Variables

Sometimes you need to store sensitive information, like API keys, database URLs, or Discord Credentials.

Environment Variables store these values outside of your codebase. They're loaded into your project when it starts, and are less likely to be exposed accidentally. Hardcoding them is a bad idea, as they risk being leaked.

Standard File

Robo projects use a file called .env. This file is in the root of your project and can be used to store sensitive information.

Here's what it may look like:

.env
# Discord Credentials
DISCORD_CLIENT_ID="your_application_id"
DISCORD_TOKEN="your_bot_token"

# My secret stuff
EXAMPLE_VARIABLE="Sshh, this is a secret!"

Variables are stored as key-value pairs, separated by an equals sign (=). The key is the name of the variable, and the value is the sensitive information. Access these values in your code using process.env.

/src/commands/secret.js
export default () => {
return process.env.EXAMPLE_VARIABLE ?? 'No secret found!'
}
warning

Be careful where you use process.env in your code. Don't expose sensitive information in logs or error messages!

Security

What's so special about Environment Variables, you ask? Well, we didn't invent them, the concept has been around for a while. Smart developers (like you) have built tools and practices around them over the years.

Here's a few reasons why they're a good idea:

  • Access Control: You can control who has access to your .env file, and who can see the values inside.
  • Audit Trails: You can track changes to your .env file, showing who changed what and when.
  • Isolation: Sensitive information is kept separate from your code, reducing the risk of accidental leaks.
  • Version Control: You can exclude .env from your version control system like GitHub.

For those using Git, know that Robo comes with an optimized .gitignore.

tip

Doppler provides a secure way to store and manage your secrets. It's awesome for sharing with teams.

Modes and Variables

Complex projects may want to use different environment variables for different cases. Robo supports this with Modes!

Suffix your .env file with the mode you want to use, like .env.production or .env.development. Robo will automatically load the correct file based on the mode you're running.

.env.production
DEBUG_STUFF="false"
EXAMPLE_VARIABLE="Hello, production!"
.env.development
DEBUG_STUFF="true"
EXAMPLE_VARIABLE="Hello, development!"

Depending on if you ran robo dev or robo start, you'll see different output.

/src/commands/secret.js
export default () => {
return process.env.EXAMPLE_VARIABLE ?? 'No secret found!'
}

Env API

Although Robo.js manages your Environment Variables automatically, it also exports an API for more control.

import { Env } from 'robo.js'
MethodDescription
ConstructorCreates a structured schema.
load()Load the .env file.
loadSync()Load the .env file synchronously.
data()Get all loaded variables.

Loading Variables

Robo.js automatically loads your .env file when your project starts. Access these in your code using process.env.

/src/events/_start.js
export default () => {
// This is EXAMPLE_VARIABLE in your .env file
console.log(process.env.EXAMPLE_VARIABLE)
}

You can load your .env file manually using the Env API.

/src/events/_start.js
import { Env } from 'robo.js'

export default async () => {
// Load the .env file
await Env.load()

// Can also be done as a blocking operation
Env.loadSync()

// This is EXAMPLE_VARIABLE in your .env file
console.log(process.env.EXAMPLE_VARIABLE)

// See only your Robo's loaded variables (ignores system ones)
console.log(Env.data())
}

Change default behavior by passing an options object.

/src/events/_start.js
await Env.load({
// The mode to load environment variables for
mode: 'production',

// Whether to overwrite existing environment variables
// Can be a boolean or an array of keys to overwrite
overwrite: true,

// The path to the environment file. Defaults to `.env`
path: '/path/to/.env'
})

Both load() and loadSync() modify process.env directly, so you can access the variables anywhere in your project, and they return an object with the loaded variables.

Again, Robo already does this for you, so you don't need to worry about it unless you want to customize the behavior.

Schema Builder

We find keeping track of every time we use process.env in our code to be a chore, so we built Env to help with that.

import { Env } from 'robo.js'

const env = new Env({
discord: {
clientId: {
env: 'DISCORD_CLIENT_ID'
},
token: {
env: 'DISCORD_TOKEN'
}
},
example: {
default: 'Nothing to see here',
env: 'EXAMPLE_VARIABLE'
}
})

This creates a schema for the Environment Variables you want organized.

// Use `get()` to access your variables
console.log(env.get('example'))

// Use dot notation for nested variables
console.log(env.get('discord.clientId'))

// Thanks to defaults, you can safely access variables
// `env.get('discord.clientId')` is the same as this, just cleaner
console.log(process.env.DISCORD_CLIENT_ID ?? 'Nothing to see here')

Now you can rename or reorganize your .env file without breaking your code as you now update it in one place.

Plus, you get type checking with TypeScript!