Feathers: @feathersjs/configuration not working when used with PM2 process manager

Created on 23 Feb 2020  路  7Comments  路  Source: feathersjs/feathers

When running Feathers with PM2, calling app.get('property') returns invalid values.

Steps to reproduce

config/default.js:

const serverMode = process.env['serverMode'] || 'worker'

module.exports = {
  serverMode
}

src/app.js

const feathers = require('@feathersjs/feathers')
const configuration = require('@feathersjs/configuration')
const express = require('@feathersjs/express')

const app = express(feathers())

// Load app configuration
app.configure(configuration())

module.exports = app

src/index.js:

const app = require('./app')

// Feathers will return an invalid value here.
const serverMode =  app.get('serverMode')

const server = app.listen(3000)

server.on('listening', () => {
  console.info('Server started.', {serverMode})
})

ecosystem.config.js (the pm2 config file):

// Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/
module.exports = {
  apps: [{
    name: 'api',
    script: 'src/index.js',
    env: {
      NODE_ENV: 'development',
      NODE_APP_INSTANCE: 0,
      serverMode: 'api'
    }
  }
  ]
}

Install pm2 with npm install -g pm2

Run pm2 from the project root (same directory as ecosystem.config.js):
pm2 start

Inspect the logs with pm2 logs --lines 100

Expected behavior

The logs should show 0|api | Server started. { serverMode: 'api' }

Actual behavior

The logs is showing 0|api | Server started. { serverMode: '{}' }

System configuration

Tell us about the applicable parts of your setup.

Module versions (especially the part that's not working):

"@feathersjs/configuration": "^4.5.1",
 "@feathersjs/express": "^4.5.1",
 "@feathersjs/feathers": "^4.5.1",

NodeJS version: 12.16.0

Operating System:
Ubuntu 18

Browser Version: Not relevant.

React Native Version:

Module Loader: require

All 7 comments

I have found the root cause of this issue.

The issue happens because one of the defined properties has the same name as another environment variable created by PM2 (the api variable).

I inspected the code for @feathersjs/configuration and found the place where the conversion takes place:

@feathersjs/configuration/lib/index.js version 4.5.1:

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const debug_1 = __importDefault(require("debug"));
const path_1 = __importDefault(require("path"));
const config_1 = __importDefault(require("config"));
const debug = debug_1.default('@feathersjs/configuration');
const separator = path_1.default.sep;
function init() {
    return (app) => {
        const convert = (current) => {
            const result = Array.isArray(current) ? [] : {};
            Object.keys(current).forEach(name => {
                let value = current[name];
                if (typeof value === 'object' && value !== null) {
                    value = convert(value);
                }
                if (typeof value === 'string') {
                    if (value.indexOf('\\') === 0) {
                        value = value.replace('\\', '');
                    }
                    else {
                        // WARNING: This line could introduce issues! It would be better to use a special syntax for denoting reusing environment variables, like '${envName}'.
                        if (process.env[value]) {
                            value = process.env[value];
                        }
                        if (value.indexOf('.') === 0 || value.indexOf('..') === 0) {
                            // Make relative paths absolute
                            value = path_1.default.resolve(path_1.default.join(config_1.default.util.getEnv('NODE_CONFIG_DIR')), value.replace(/\//g, separator));
                        }
                    }
                }
                result[name] = value;
            });
            return result;
        };
        const env = config_1.default.util.getEnv('NODE_ENV');
        const conf = convert(config_1.default);
        if (!app) {
            return conf;
        }
        debug(`Initializing configuration for ${env} environment`);
        Object.keys(conf).forEach(name => {
            const value = conf[name];
            debug(`Setting ${name} configuration value to`, value);
            app.set(name, value);
        });
        return conf;
    };
}
exports.default = init;
if (typeof module !== 'undefined') {
    module.exports = Object.assign(init, module.exports);
}
//# sourceMappingURL=index.js.map

I had to change the ecosystem.config.js and rewrite the name of the application:

// Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/
module.exports = {
  apps: [{
    name: 'api-server',  // THIS LINE HAS BEEN CHANGED TO FIX THE ISSUE.
    script: 'src/index.js',
    env: {
      NODE_ENV: 'development',
      NODE_APP_INSTANCE: 0,
      serverMode: 'api'
    }
  }
  ]
}

I would suggest changing the behavior of Feathers/Configuration in order to avoid possible issues.
If a config file want to declare a variable that uses the environment variable, it could do something like:

serverURL: "${SERVER_URL}"

@daffl, please have a look here, it's an important issue that can cause subtle bugs.

@averri does the local auth works with pm2? I had to ask you here because I am also trying to deploy using pm2 and runs but local auth with rest api always returns 'Invalid login', even with right credentials.

But if i run it with forever or npm it works.

Hi @yohane55, yes, it does work for sure. I'm now using pm2 frequently. Here is one example of my ecosystem.config.js:

// Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/
module.exports = {
  apps: [{
    name: 'api-server',
    script: 'src/',
    watch: ['src'],
    listen_timeout: 30000,
    node_args: "--inspect --inspect-port=5000",
    env: {
      NODE_ENV: 'development',
      NODE_APP_INSTANCE: 0,
      serverMode: 'api',
      useRedis: false,
      redisURI: 'redis://localhost:6379',
      bindPort: 4000,
      useRateLimiter: true
    }
  },
    {
      name: 'worker-server',
      script: 'src/',
      watch: ['src'],
      listen_timeout: 30000,
      node_args: "--inspect --inspect-port=5001",
      env: {
        NODE_ENV: 'development',
        NODE_APP_INSTANCE: 1,
        serverMode: 'worker',
        useRedis: false,
        redisURI: 'redis://localhost:6379',
        bindPort: 4001,
        useRateLimiter: false
      }
    }
  ]
}

Do you have an authentication hook? If no, configure one and debug the before.all to see what credentials are coming from the client. You may also note something very important - in the configuration of the authentication service you need to escape the username field like this:

authentication: {
   ...
    local: {
      usernameField: "\\username",
      passwordField: "password"
    }
  },

@averri Thank you so much and you are right I had to escape"\\username" and now it is working fine.

I have this problem with a feather app initially built with the version 2.

So I tried to upgrade to v4 but I still have this problem. I don't have the any serverMode in my app and if I try to change the name nothing changes.

With the username escaped properly the authentication is still not working on my instance of feathers when started by pm2.
With npm start or node src/ all good. As soonas i change the start to be made by pm2 authentication return not Authorized all the times.
Running on Linux, not windows.

@daffl Can you help ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

intumwa picture intumwa  路  3Comments

andysay picture andysay  路  3Comments

rstegg picture rstegg  路  3Comments

NetOperatorWibby picture NetOperatorWibby  路  4Comments

RickEyre picture RickEyre  路  4Comments