Search code examples
javascriptnode.jsexpressexpress-generator

Express Generator - TypeError: app.set is not a function


I first created an API with Node/Express by myself to learn from a 'naive' way to the way most programmer do. It's was working well and I decided to try express-generator.

After setting everything up the app works fine.

I added my bunch of code (mainly in app.js and importing few routes to try), I haven't change anything in the bin/www where express is setup.

But at launch I got this error pointing at this particularly file bin/www :

app.set('port', port);
    ^

TypeError: app.set is not a function

I don't know why this part of code automatically generated didn't want to work now.

Here the 2 principals files

bin/www : (generated-untouched)

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('API:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

app.js :

// Module dependencies
const express = require('express');
const cluster = require('express-cluster'); // fork the service on different thread
const helmet = require('helmet'); // Secure HTTP header
const cors = require('cors');
const path = require('path');
const bodyParser = require('body-parser'); // Help to easily parse the body of req/res
const port = process.env.PORT || 3000;
const mongoose = require('mongoose'); // Manage MongoDB request

cluster(function(worker) {
  var app = express();

  // MongoDB config
  const config = require('./misc/config/index'); // Config variable like MongoDB credential
  mongoose.Promise = global.Promise;
  mongoose.connect(config.getDBConnectionString()); // Config to cnx to mongodb
  // mongoose.connect(config.getDBConnectionString(), { config: { autoIndex: false } });

  // Middleware
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({extended: true}));
  app.use(helmet.frameguard()); // Default Value - Help to secure request by putting some setting in the header
  app.use(cors()); // Handling Cross Origin Ressource Sharing

  // Logfile
  const log = require('./misc/log/log');
  app.use(log);

  // Config Landingpage to /
  app.use('/assets', express.static(path.join(__dirname, 'public')));
  app.set('view engine', 'ejs');

  // Entry point
  const entryPoint = require('./routes/entryPoint');
  app.get('/', entryPoint.index);
  app.get('/api', function (req, res) {
    res.redirect(301, '/');
  })

  // API Key handler
  const auth = require('./misc/auth/auth');
  app.use(auth);

  // List
  const list = require('./routes/list/listRouter'); // Get List endpoints
  // app.use('/api/list', list);

  // Map
  const map = require('./routes/map/mapRouter'); // Get List endpoints
  // app.use('/api/map', map);

  module.exports = app;
}, {count: 2})

I export app which include express(). So there is something i'm missing here but I don't see what.


Solution

  • The issue is caused by this setup:

    cluster(function(worker) {
      var app = express();
      ...
      module.exports = app;
    }, { ... });
    

    Because cluster will call the "worker function" asynchronously, the export is done asynchronously as well, which is too late for bin/www. Aside from that, the worker function will run in a separate process, which also complicates things.

    Because bin/www is relatively simple in terms of what it does, you could opt for moving its contents (or at least the gist of what it does) to app.js, and start your app with node app.js. You could even replace bin/www with a simple script:

    #!/usr/bin/env node 
    
    require('../app');
    

    Alternatively, you can leave your code as-is and rely on an external program to provide load balancing across the available CPU's. A popular solution for that is pm2, although it doesn't work well for everyone (bit of a hit-and-miss, it seems).

    If you need to pass environment variables, you can do so from the command line:

    $ env API_KEY=XXX pm2 start app.js
    

    Or create a configuration file for your app.

    There's also a package called dotenv that will allow your Node app to read environment variables from a file directly.