sveltejs / sapper

The next small thing in web development, powered by Svelte
https://sapper.svelte.dev
MIT License
6.99k stars 432 forks source link

Allow process.env.PORT to be defined via .env file #1254

Closed martinburger closed 4 years ago

martinburger commented 4 years ago

I would like to use dotenv to configure the port number the app listens on via a .env file.

I minimal implementation of that approach follows these steps:

  1. Start new project using npx degit "sveltejs/sapper-template#rollup" sapper-dotenv-port.
  2. Install dotenv via npm install dotenv.
  3. Create .env file with content PORT=5000.
  4. Change file src/server.js as follows:
--- a/src/server.js
+++ b/src/server.js
@@ -6,6 +6,9 @@ import * as sapper from '@sapper/server';
 const { PORT, NODE_ENV } = process.env;
 const dev = NODE_ENV === 'development';

+const result = require('dotenv').config({ debug: true });
+console.log('dotenv result:', result);
+
 polka() // You can also use Express
        .use(
                compression({ threshold: 0 }),

I would expect that the Sapper app listens on port 5000. However, it listens on default port 3000 instead. The console output shows the reason:

❯ npm run dev

> TODO@0.0.1 dev /[...]/sapper-dotenv-port
> sapper dev

✔ server (952ms)
✔ client (959ms)
> Listening on http://localhost:3000
[dotenv][DEBUG] did not match key and value when parsing line 2:
[dotenv][DEBUG] "PORT" is already defined in `process.env` and will not be overwritten
dotenv result: { parsed: { PORT: '5000' } }
✔ service worker (28ms)

As far as I understand the cause of process.env.PORT being already defined, this is due to the following two lines in the Sapper code base:

  1. PORT: this.port at src/api/dev.ts#L287
this.proc = child_process.fork(`${dest}/server/server.js`, [], {
  cwd: process.cwd(),
  env: Object.assign({
    PORT: this.port
  }, process.env),
  stdio: ['ipc'],
  execArgv
});
  1. process.env.PORT = process.env.PORT || ${opts.port || 3000}; at src/cli.ts#L179
fs.writeFileSync(launcher, `
  // generated by sapper build at ${new Date().toISOString()}
  process.env.NODE_ENV = process.env.NODE_ENV || 'production';
  process.env.PORT = process.env.PORT || ${opts.port || 3000};
  console.log('Starting server on port ' + process.env.PORT);
  require('./server/server.js');
`.replace(/^\t+/gm, '').trim());

Thus, the definition of process.env.PORT is hard coded. That way, it is not possible to redefine its value using dotenv.

It would be great if I could define that port number in my .env file.

antony commented 4 years ago

I don't know if it's useful to you, but I simply add PORT=1234 to my dev task in package.json.

martinburger commented 4 years ago

I don't know if it's useful to you, but I simply add PORT=1234 to my dev task in package.json.

True, one can add PORT=1234 to package.json -- or alternatively to the script that calls npm start to run the application.

In my case, I use systemd and I have added line

Environment=PORT=1234

to the unit configuration that launches my Sapper app.

However, I would prefer a unified way to configure my various Node.js apps. Usually, I configure those apps via dotenv. That way, I know directly where I find the respective configuration.

thgh commented 4 years ago

@martinburger you should put require('dotenv').config({ debug: true }); above const { PORT, NODE_ENV } = process.env;. I haven't checked, but that's how I usually do it and it works afaik.

Edit: Looks like you need require('dotenv').config({ override: true }) which is suboptimal.

martinburger commented 4 years ago

I think the trick is to use Node.js' --require(-r) command line option to preload dotenv in the production environment:

diff --git a/package.json b/package.json
index 55e44ad..caf661f 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
     "dev": "sapper dev",
     "build": "sapper build --legacy",
     "export": "sapper export --legacy",
-    "start": "node __sapper__/build",
+    "start": "node -r dotenv/config __sapper__/build",
     "cy:run": "cypress run",
     "cy:open": "cypress open",
     "test": "run-p --race dev cy:run"

That way, Sapper takes the PORT number specified in the .env file into account.

diff --git a/src/server.js b/src/server.js
index c77f593..d558ead 100644
--- a/src/server.js
+++ b/src/server.js
@@ -3,6 +3,14 @@ import polka from 'polka';
 import compression from 'compression';
 import * as sapper from '@sapper/server';

+const dotenvResult = require('dotenv').config();
+if (!dotenvResult.error) {
+   console.log(`PORT from .env file: ${dotenvResult.parsed.PORT}`);
+} else {
+   console.log('No .env file.');
+}
+console.log(`PORT from process.env: ${process.env.PORT}`);
+
 const { PORT, NODE_ENV } = process.env;
 const dev = NODE_ENV === 'development';

After building Sapper with npm run build I get the following results.

No .env file:

$ npm start
[...]
Starting server on port 3000
No .env file.
PORT from process.env: 3000

No PORT set in existing .env file:

$ npm start
[...]
Starting server on port 3000
PORT from .env file: undefined
PORT from process.env: 3000

PORT set to 5000 in .env file:

$ npm start
[...]
Starting server on port 5000
PORT from .env file: 5000
PORT from process.env: 5000

Override PORT to 6000 via command line:

$ PORT=6000 npm start
[...]
Starting server on port 6000
PORT from .env file: 5000
PORT from process.env: 6000

If you use dotenv in that way, you might want to issue a warning like the following one to avoid some confusion caused by inconsistent port numbers:

diff --git a/src/server.js b/src/server.js
index c77f593..1c9de3f 100644
--- a/src/server.js
+++ b/src/server.js
@@ -3,9 +3,18 @@ import polka from 'polka';
 import compression from 'compression';
 import * as sapper from '@sapper/server';

+const dotenvResult = require('dotenv').config();
+
 const { PORT, NODE_ENV } = process.env;
 const dev = NODE_ENV === 'development';

+if (dev && !dotenvResult.error && dotenvResult.parsed.PORT && dotenvResult.parsed.PORT !== process.env.PORT) {
+   console.warn(`PORT = ${dotenvResult.parsed.PORT} is specified in .env file and Sapper runs in development mode.`);
+   console.warn(`The there specified port number differs from process.env.PORT: ${process.env.PORT}`);
+   console.warn(`You might want to run Sapper via '$ PORT=${dotenvResult.parsed.PORT} npm run dev' to use the same PORT as in .env file.`);
+   console.warn('Hint: You can set PORT via .env file in production mode if you start Sapper via "node -r dotenv/config __sapper__/build" (see package.json).');
+}
+
 polka() // You can also use Express
    .use(
        compression({ threshold: 0 }),

This will warn you if you run Sapper in development mode and the .env file specifies PORT number:

$ npm run dev
[...]
✔ server (948ms)
PORT = 5000 is specified in .env file and Sapper runs in development mode.
The there specified port number differs from process.env.PORT: 3000
You might want to run Sapper via '$ PORT=5000 npm run dev' to use the same PORT as in .env file.
Hint: You can set PORT via .env file in production mode if you start Sapper via "node -r dotenv/config __sapper__/build" (see package.json).
> Listening on http://localhost:3000
[...]
antony commented 4 years ago

The way to cause sapper to consider dotenv's configuration runs is as follows:

npm i -g dotenv-cli

Then edit your package json:

"scripts": {
  "dev": "dotenv sapper dev",
}

That way, the contents of your .env file will be considered at application launch time.

Huiet commented 3 years ago

I've banged my head on my keyboard for a while before figuring out my .env file wasn't working with the port because I had a semicolon at the end of the PORT=3000; variable declaration....