Express 4, NodeJS, AngularJS routing
Asked Answered
D

1

5

I am using Express 4 to host my AngularJS app on my backend, with Nginx as my frontend server. However html5 mode does not seem to work, as I will get a Cannot /GET error when I try to enter the page link (e.g. http://localhost/login) via the browser. Is there any routing configuration I need to do for my Express/Nginx? Here's my config code:

Express 4:

var express = require('express'),
    app = express(),
    server = require('http').Server(app),
    bodyParser = require('body-parser'),
    db = require('./db'),
    io = require('./sockets').listen(server),
    apiRoutes = require('./routes/api'),
    webRoutes = require('./routes/web');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));
app.use('/api', apiRoutes);
app.use(express.static(__dirname + '/public'));

server.listen(3000, function() {
  console.log('Listening on port %d', server.address().port);
});

AngularJS:

'use strict';
var nodeApp = angular.module('nodeApp',['ngRoute']);

nodeApp.config(function($routeProvider, $locationProvider, $controllerProvider) {
  $routeProvider.when('/', {
    templateUrl: 'partials/home.html'
  }).when('/login', {
    templateUrl: 'partials/login.html'
  });
  $locationProvider.html5Mode(true);

  nodeApp.controllerProvider = $controllerProvider;
});

Nginx:

# the IP(s) on which your server is running
upstream test-app {
  server 127.0.0.1:3000;
}

# the nginx server instance
server {
  listen 0.0.0.0:80;
  server_name test-app.cloudapp.net;
  access_log /var/log/nginx/test-app.log;

  # pass the request to the nodejs server with correct headers
  location / {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Nginx-Proxy true;

    proxy_pass http://test-app/;
    proxy_redirect off;
  }
}
Demoiselle answered 11/8, 2014 at 3:10 Comment(0)
E
20

I'm assuming you are using a "single page" angular app, so one html page that uses ng-view to load all the other partials.

In this case you need to do something like this:

Express 4:

var express = require('express'),
    app = express(),
    server = require('http').Server(app),
    bodyParser = require('body-parser'),
    db = require('./db'),
    io = require('./sockets').listen(server),
    apiRoutes = require('./routes/api'),
    webRoutes = require('./routes/web');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));
app.use('/api', apiRoutes);
app.use(express.static(__dirname + '/public'));
// Here's the new code:
app.use('/*', function(req, res){
  res.sendfile(__dirname + '/public/index.html');
});

server.listen(3000, function() {
  console.log('Listening on port %d', server.address().port);
});

The problem you're facing is that even though you have routes setup for '/login' before the routes are fired they need to be loaded. So the server tries to find a match for the route '/login' which it can't returning the 404. In the case of single page angular apps all the routes you use in routing must be caught by a route, app.get('/*', ... in this case, and then return the main angular.js html page. Note that this is the last call so it will be evaluated last, if you put it first it will prevent all the subsequent rules from running as express just runs the handler for the first rule it encounters.

Ergosterol answered 11/8, 2014 at 3:52 Comment(8)
Thanks for your reply! I had to make some slight changes to the code to get it working, mainly from app.get to app.use. Perhaps that's because of differences in Express 3/4?Demoiselle
That is probably just my mistake, I normally render jade templates instead of just outputting html - I just incorrectly assumed it worked the same if I adapted the code a little.Ergosterol
@SimeonCheeseman I don't understand the use of __dirname + 'public/index.html' in your code snippet. If I have my partials at views/partials how will the server find the page am I requesting? It looks like this code would look for any route at all and always send back index.html, if it didn't match a route above '/*'. How does this load new partials into the index.html page?Botanize
@SimeonCheeseman, how would I need to change the above code if I am using a home.jade to render all of my views. That is, home.jade contains ng-view that swaps partials.Nonconformance
@Nonconformance From memory you just change this line: res.sendfile(__dirname + '/public/index.html'); to the render jade page version. It's got a little more setup but see here: expressjs.com/en/guide/using-template-engines.htmlErgosterol
@SimeonCheeseman, thanks, I'll have a look at that. More generally, is that a common practice to separate the routes between express/node and Angular? That is, I understand the need to do it, but how do people decide which route does what? Is it mostly authentication for Express/Node and the rest through Angular? Are there other considerations?Nonconformance
@Nonconformance I've no idea what common practice is outside my work. Normally I use the above route to serve the Angular app which does all the front end routing and everything else (ie data) is done via restful routes - this way you can turn your express/node app into just a REST API and the Angular pages can be put on a CDN somewhere which would speed up loading your site. If you want to mix Serverside and client side route rendering I'd recommend looking at meteor and how they do it.Ergosterol
This guys is my hero haha thank you for the clear concise answer!Darlleen

© 2022 - 2024 — McMap. All rights reserved.