Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Use dotenv for environment vars? #122

Closed
arxpoetica opened this issue Feb 14, 2018 · 27 comments
Closed

Use dotenv for environment vars? #122

arxpoetica opened this issue Feb 14, 2018 · 27 comments
Labels

Comments

@arxpoetica
Copy link
Member

(Just putting this somewhere so I remember.)

This is one type of best practice. For example, I use this as a config.js file, which I can then load anywhere.

const dotenv = require('dotenv-extended')
const dotenvExpand = require('dotenv-expand')
const dotenvParseVariables = require('dotenv-parse-variables')
let config = dotenv.load({ silent: false })
config = dotenvExpand(config)
config = dotenvParseVariables(config)
config.self = true
module.exports = config

It's the equivalent of doing process.env.WHATEVS, but it helps manage enviro vars both local and in production (such as a PORT).

@Rich-Harris
Copy link
Member

Should we close this @arxpoetica? I think this is probably an app-level concern, rather than something Sapper needs to worry about

@arxpoetica
Copy link
Member Author

Sure, that's fine. I'll close.

@Rich-Harris
Copy link
Member

Ok, I may have changed my mind about this. Managing environment variables is such a common chore that I've come round to thinking that maybe it makes sense to have an officially recognised 'correct' way to do it.

My thinking is that if you have .env and/or .env.defaults files in your CWD, then sapper build, sapper export and sapper dev should load them (.env.defaults first, then the more specific .env file, probably using dotenv under the hood). The generated launcher file (build/index.js) should also load the variables:

// generated by sapper build at 2018-08-01T22:14:14.378Z
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
process.env.SAPPER_DEST = __dirname;
process.env.PORT = process.env.PORT || 3000;

+require('sapper/env.js').setup();

console.log('Starting server on port ' + process.env.PORT);
require('./server.js');

.env shouldn't be checked in to version control; typically, in production there would be some other mechanism for injecting env vars, and sapper/env.js wouldn't do anything if those files didn't exist.

Good idea? Bad idea? Things I've overlooked?

@Rich-Harris Rich-Harris reopened this Aug 1, 2018
@Rich-Harris
Copy link
Member

As @johnmuhl says, an alternative is to do this:

import 'dotenv/config';

Also, the dotenv readme argues against the dotenv-extended approach of having multiple env files (i.e. just have .env, don't also have .env.defaults). Not sure where I land.

@arxpoetica
Copy link
Member Author

arxpoetica commented Aug 1, 2018

...just have .env, don't also have .env.defaults...

Actually, The Twelve-Factor App which dotenv references argues against grouped "environments" (test, dev, production, staging, etc), which I can agree with, but this doesn't mean defaults aren't a good idea.

Arguably, if it's a default, one could just encode it during startup and not even have it in a .env file...but I personally favor .env.defaults for ease of use.

@lukeed
Copy link
Member

lukeed commented Aug 1, 2018

Single file FTW 👍 IMO default values are either belong in a config object (due to not being environmentally driven), or are environment-specific and live in that one-off file.

@elcobvg
Copy link
Contributor

elcobvg commented Aug 1, 2018

I use a config.js which merges the defaults and .env values like so:

require('dotenv').config()

module.exports = {

  appId: process.env.APP_ID,
  port: process.env.PORT || 3000,
  url: process.env.APP_URL || 'http://localhost',
  allowFrom: process.env.ALLOW_FROM,
  mapApiKey: process.env.MAP_API_KEY,
  defaultLocale: process.env.DEFAULT_LOCALE || 'en',
 ...
}

@arxpoetica
Copy link
Member Author

default values either belong in a config object

@lukeed devil's advocate, what's the difference between two env files

or just two config files for that matter? what you're describing is effectively the same thing, just different file extension names.

@lukeed
Copy link
Member

lukeed commented Aug 1, 2018

Haha 😆 The difference is the environment that they're run in.

And the difference in having the same environment split into multiple files (or not) is just a preference for having a single source of truth.

It's a lot easier to guard one thing with your life than to guard multiple at the same time. And, at least for me, it'd be harder to remember/maintain which files are shareable, which ones are super sensitive, which need to be encrypted, etc etc.

At the end of the day, it doesn't really matter. I think the dotenv team recommends against composing env-files because it is harder to keep track of, and (maybe) because it's more work for their module to do.

@arxpoetica
Copy link
Member Author

@lukeed I don't think we're understanding each other. I'm not talking about different environments.

I actually agree with the Twelve-Factor App that multiple environment files aren't good. I was strictly talking about defaults, i.e., what's the best place to put them.

@Rich-Harris
Copy link
Member

I think I'm pro-single file — it's easy enough to have defaults in a separate config file, as @elcobvg showed. And if that file is .env, then import 'dotenv/config' works as-is, making it a perfectly good solution that doesn't require any changes to Sapper. All we really need to do is add that to our list of tutorials to create (on which note sveltejs/sapper-legacy.svelte.dev#25).

So I'll close this issue again. Thanks everyone!

@lukeed
Copy link
Member

lukeed commented Aug 2, 2018

@arxpoetica Perhaps not, sorry!

For default values (what I will also call static values), I will go about it one of two ways, depending on how many total environment variables I have.

If I have a lot (couple dozen?), I typically feel like I need some more organization, so I will take the approach that @elcobvg mentioned. By constructing the config object, I get to embed the default config values within the code base in a place that's immediately relevant (IMO).

For smaller projects, I will generally just include the default values directly within .env.example, which I always include in git, regardless of project size, as it gives (and describes) the keys necessary for the project to function. This is purely for documentation as dotenv will never pick it up.

This might be a bad practice or something along those lines, but it's what I picked up after years of doing PHP work. :D

@joelparkerhenderson
Copy link

joelparkerhenderson commented Aug 21, 2018

My preference is to enable .env to be a file or a directory; if it's a directory, then read all the files in that directory.

This provides the ability for teams to choose whether they want one file (as recommended by the 12-Factor) or multiple files (which tends to be easier to secure and also more flexible).

@kaleidawave
Copy link

So use dotenv for environment variables? Will we see an official implementation later?

@imbolc
Copy link

imbolc commented Aug 8, 2019

Now dotenv can't overwrite PORT if it called on top of server.js

@benwinding
Copy link

Any instructions on how to set up sapper with dotenv?

I'm getting a node error during the compile.....

Module not found: Can't resolve 'fs'

@Sureiya
Copy link

Sureiya commented Oct 28, 2019

It seems the way sapper builds things probably means we need some specific guidance on how to do this, as the common examples are giving the Module not found: Can't resolve 'fs' errors.

@marcobrunodev
Copy link

marcobrunodev commented Nov 9, 2019

I had the same problem and I tested a few things to make it work:

  • The first thing I tested was use 'import 'dotenv/config' in my client. The error changed to this (but the client runs ok):
preferring built-in module 'fs' over local alternative at 'fs', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
preferring built-in module 'fs' over local alternative at 'fs', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
'fs' is imported by node_modules/dotenv/lib/main.js, but could not be resolved – treating it as an external dependency
preferring built-in module 'fs' over local alternative at 'fs', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
'fs' is imported by fs?commonjs-external, but could not be resolved – treating it as an external dependency
preferring built-in module 'path' over local alternative at 'path', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
preferring built-in module 'path' over local alternative at 'path', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
'path' is imported by node_modules/dotenv/lib/main.js, but could not be resolved – treating it as an external dependency
preferring built-in module 'path' over local alternative at 'path', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
'path' is imported by path?commonjs-external, but could not be resolved – treating it as an external dependency
  • Then I tried to set preferBuiltins: false at resolve in rollup.config.js. This gave me other error (the client still runs ok):
    Could not load fs (imported by /home/marcobruno/github/collabcode/opensource/collabcodetraing/frontend/node_modules/dotenv/lib/main.js): ENOENT: no such file or directory, open 'fs'

  • Last thing I tried was to use a require instead a import: require('dotenv/config') and then it worked without warnings! The above steps aren't needed this way. Just the require('dotenv/config');

If anyone find how to fix it using import it'll be awesome.

@kaleidawave
Copy link

There are two ways to use dotenv. Both require a .env file at the top of the root. The first is to use it as a module with npm i dotenv and then to load the files I use:

// Using import
import { config } from "dotenv";
config();

// Or using require
require('dotenv').config();

(remember this call needs to be done in server.js as client.js does not have access to fs)

If those do not work you can dotenv-cli (which you need install globally). From there you do not need to modify any code, you just need to prepend any commands with dotenv. So your start script becomes dotenv npm start (and you can modify npm scripts so you don't have to write dotenv everytime).

I have had success with both approaches. BUT it is important to note that anything on the frontend does not have access to process.env as it is in the nodejs library. So as for now you can only "inject" environment variables into specifically server activities. It may be possible by actually injecting them with a webpack plugin as I know this is possible in React. But there is little reason as environment variables are mostly used for private keys... which you would not want on the public frontend anyway?

@Sureiya @benwinding @MarcoBrunoBR

@roppa
Copy link

roppa commented Mar 12, 2020

Any way to pass .env variables to the client side?

@burningTyger
Copy link
Contributor

You can pass them as session data on to the client. Please come and talk to us on the discord channel for further discussions.

@sudhanshuraheja
Copy link

sudhanshuraheja commented Apr 27, 2020

You can use dotenv on server.js and pass this data to the client

import dotenv from "dotenv";
const config = dotenv.config();

const { API_PATH } = process.env;

polka()
  .use(
    sapper.middleware({
      session: (req, res) => ({
        apiPath: API_PATH,
      }),
    })
  )

Then you can use it in a svelte file like this

<script context="module">
  export async function preload({ params }, session) {
    const res = await this.fetch(`${session.apiPath}/word/${params.word}`);
    .....
  }
</script>

@martinburger
Copy link

Any way to pass .env variables to the client side?

I use a patch inspired by Antony Jones' "Svelte Most Wanted" talk.

It reads config variables and values using dotenv and replaces all occurrences of process.env.variable with the respective value.

For instance, you can declare

API_BASE_URL=http://127.0.0.1:3000

in your .env file.

If you use string process.env.API_BASE_URL somewhere in your code, Rollup's replace plugin will substitute that string with string http://127.0.0.1:3000 while bundling.

@plashenkov
Copy link

Hi there!

Using this patch from @martinburger comment means that all the variables from .env will be compiled into the build, am I right? So, the app will not read variables from .env file/files on start. After changing some vars you'll need to rebuild the app.

Maybe this is what some devs really need (I don't know), but if we want the app to read vars from .env files on startup, we can use dotenv or dotenv-flow package, but we will not be able to customize PORT, for example (since sapper defines it before we can initialize dotenv or dotenv-flow package).

And we really can use dotenv-cli package here — just prepend npm scripts with dotenv -- [command], and the command will get all env vars from .env file.

dotenv -c -- [command] is similar to what dotenv-flow package does, i.e. reads .env, .env.local, .env.<environment> and .env.<environment>.local files — this is a common approach which is used by many frameworks including Next.js, create-react-app, Symfony, etc.

@jly36963
Copy link

jly36963 commented Dec 1, 2020

I can open an issue if it makes sense to, but I can't seem to get env vars to work in server routes.

In .env:

GENIUS_TOKEN="I am a secret"

In src/server.ts:

require("dotenv").config();

In src/routes/api/genius/get-lyrics.ts:

const geniusToken: string | undefined = process.env.GENIUS_TOKEN;

process.env.GENIUS_TOKEN works as expected in server.ts, but not in get-lyrics.ts. Instead, it is undefined. Other than using require("dotenv").config(); in every server route, is there a way to get secrets into the server routes?

@migbash
Copy link

migbash commented Aug 28, 2021

@jly36963 here -> https://mariosfakiolas.com/blog/manage-environment-variables-in-a-sapper-application/

@xpluscal
Copy link

xpluscal commented Oct 7, 2021

@jly36963 here -> https://mariosfakiolas.com/blog/manage-environment-variables-in-a-sapper-application/

That's great. The only thing I find it hard to get working with is in context="module" cases.
stores() isn't initialized there. How can I access the .env vars in the module itself? (e.g. to use it in function calls there)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests