Nginx, NodeJS and SPAs

Jose I Santa Cruz G
3 min readOct 1, 2018

Most of the work I do are single page web applications (with Angular), that usually end compiled as desktop (using NW js or Electron) or mobile apps (using Ionic). Web applications, with NodeJS backends, end published in some Linux server with Nginx, and for simplicity are served thru it's backend.

So the architecture is something like the image above.

The problem I have faced is that when using Angular's router, every internal page is accesible when entering directly to the first/default page, but when trying to access directly the NodeJS backend shows a

"GET /app/internal_route not found"

message (or something like that).

Almost all tutorials refer to nginx configurations when the SPA is served directly via nginx:

Please note the following:

  1. I'm using nginx, so this is a sample available_sites configuration. For further information explore the /etc/nginx/ folder and do some Google searching.
  2. I prefer to place all my SPAs and the root www folder inside /opt . This is just my personal preferences, not saying it should be this way. The only big pro of doing this is that if you have to make a full OS upgrade, you'll know what to keep (never forget the /opt). Same happens if you have to move from one server to another; can't say how many times I forgot to move the default web server folders.
  3. SSL configuration is provided by LetsEncrypt.

Lines 15–18 make a SPA work inside the chosen folder, in this case /opt/some_app/www , considering the app to be a static HTML+CSS+JS app and supposing the api (if required) being elsewhere.

Line 17 specifies that any request inside webpath /some_app is handled by the /some_app/index.html file.

In my case I required the SPA to work from my NodeJS backend. So the confiration required should be something like this:

Note that I intentionally skipped the SSL parts just to make copy & pasting a little more entertaining

In this case my main app, NodeJS + Express , is served in the internal port 3218, but accesible through nginx at port 8123.
Lines 13–21 enable CORs headers, but nevertheless you'll have to enable CORs on the NodeJS app.
Lines 22–23 enable the internal redirection, while lines 25–28 ensure WebSockets work through this "tunnel".

But again, while testing my app everything seemed to work without any problems… until I tried to access an internal app route directly:

Cannot GET /internal_route

So there was clearly something not working as expected. Internal routes were all catched by the Express app, instead of being handled by nginx as one would likely believe. So our missing part is to tell the NodeJS app to handle internal routes.

In Express this is done by adding a wildcard route:

// Inside your NodeJS + Express app
// (supposing your SPA lives inside the app's www folder)
app.use('/', express.static(path.join(__dirname, 'www')));
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'www', 'index.html'));
});

and this way any route that's not explicitly handled by the NodeJS app will be solved by Angular via it's index.html file. Just be sure that in the internal routing you also have a default route fallback, for eg.:

const routes: Routes = [  { path: 'login', component: LoginComponent },
// ... other routes
{ path: '',
redirectTo: '/login',
pathMatch: 'full' },
{ path: '**', component: NotFoundComponent }
];

So for an inside out review:

  1. The Angular app must have a default route handler ('**')
  2. so must your ExpressJS app.
  3. If the app is not redirected to another server, nginx must handle all requests with the index.html file.

And that's all folks!
Not so hard, no rocket science, just a little patience and you'll have a nice setup for your app.

If you liked the article follow, comment or clap.
(claps would be nice :) )

--

--

Jose I Santa Cruz G

Polyglot senior software engineer, amateur guitar & bass player, geek, husband and dog father. Not precisely in that order.