How to best structure an Express V4.11+ project with Socket.IO?

Jamie Street picture Jamie Street · Feb 8, 2015 · Viewed 10.1k times · Source

I've used the latest version of the express-generator (link) to generate a boilerplate Express 4.11.2 project.

I'm trying to figure out the best way of setting up Socket.IO 1.3.3 with the changes that have been made a few major versions back in the Express framework, where the http server was moved into its own bin/www file.

I'm getting confused because it seems like I need the express server in app.js, before it gets exported to bin/www.

I've tried Googling for a few hours now and come up with nothing, it seems that the Express framework is moving so fast that all of the previous articles are outdated.

Any help would be greatly appreciated!

I've included the two files in question below so you can see what I'm working with after generating the boilerplate express project.

bin/www

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('Express-Socket: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

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

Answer

BernieDADA picture BernieDADA · May 7, 2015

A lot of info about socket.io and express are out of date due to their popularity and fast changing pace.

This is what I ended up doing, by no means it's the best.

I would create a sockets.js at the same level as app.js, so you can separate all socket.io initialization logic.

var sockets = {};

sockets.init = function (server) {
    // socket.io setup
    var io = require('socket.io').listen(server);
    io.sockets.on('connection', function (socket) {
        console.log('socket connected');
        // other logic
    });

}

module.exports = sockets;

And in your bin/www file, you can initialize socket.io like this:

#!/usr/bin/env node
var debug = require('debug')('yourProject');
var app = require('../app');
var sockets = require('../sockets')

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

var server = app.listen(app.get('port'), function() {
    debug('Express server listening on port ' + server.address().port);
});

sockets.init(server);