Run Multiple Node Apps on One Server with Sub-URIs and Socket.io

I spend hours googling shit to find this easiest way.

What I wanna do is:

Say I have my node app running on port 3000.

  1. Instead of visit mydomain.com:3000, I wanna to visit mydomain.com/appname/.
  2. So I can visit another app by mydomain.com/app2/.

Summary:

[ ] Get my node app and NginX ready.
[ ] Set a sub URI to proxy to our node app.
[ ] Create NginX upstream to handel all the sockets connection.
[ ] Change some app code to match this scheme.
[ ] Since we can only have one socket port on our server, we must seperate different apps by different socket io name space (nsp);
[ ] Moreover, socketio will nolonger share the same port with express app.

Before I find this, I tried Passenger + Apache, Passenger + NginX. The problem I encountered was socket.io. They just don’t work well with socket.io, and saying I must change my router blah blah.

This documentation will focus on Debian 9.0 + NginX 1.10.3

Get my node app and NginX ready.

Skip.

Set a sub URI to proxy to our node app.

In my case, my app is called bully and my domain name is hellidea.com.

For the convenience of managing multiple apps, we can create config files respectively.

In Debian, the location to put config files is /etc/nginx/sites-enabled/.

We need root to edit file here.

sudo vim /etc/nginx/sites-enabled/bully.conf
server {
    listen 80;
    server_name hellidea.com;

    location ^~ /bully/ {
        rewrite ^/bully/(.*)$ /$1? break;
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
     }
}

This means, when user visits hellidea.com:80/bully/, pass all the traffic to localhost:8888, which my bully app is listening.

After this, I can see my app by visiting hellidea.com/bully/. However, the socket.io still doesn’t work.

Get socket.io to work.

If you were serving client socketio script by node,

# in public/index.html
<script src="/socket.io/socket.io.js"></script>

you will see this error:

# Error
GET http://hellidea.com/socket.io/socket.io.js net::ERR_ABORTED 502 (Bad Gateway)

Because of Nginx sub URI, this no longer works.

I chose a workaround by pointing the source file to a cdn:

# inside public/index.html
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>

Then a 2nd error:

Cannot GET /socket.io/?EIO=3&transport=polling&t=LdmmKYz

It cannot talk to our server.

Let’s say, instead of using the same port with express app, we give one port, say 5000, to all the socketio instances.

Then all our app will talk through port 5000, but in different namespaces.

Creat a upstream at port 5000;

# Add this inside http{} of file /etc/nginx/nginx.conf

upstream socketio {
    ip_hash;
    server 127.0.0.1:5000;
    # you can do load balance here if you will
}

route all socketio to this up stream:

# Add this inside server{} of file /etc/nginx/sites-enabled/bully.conf
    location / {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://socketio/socket.io/;
    }

Inside server.js, tell socket.io to listen to port 5000 with a unique namespace, which we call bullySocket;

const io = require('socket.io').listen(5000);
const web = io.of('/bullySocket');

Inside public/index.js, tell socket to connect to this special name space.

let socket = io.connect('http://hellidea.com/bullySocket');

restart NginX and you node app.

Now it works like a charm!

Leave a Reply

Your email address will not be published. Required fields are marked *