Victor
Victor

Reputation: 762

AngularJS and NodeJS http-server: rewrite URL

I'm using NodeJS http-server to code an AngularJS App.
I'm having a problem. When I try to access directly the url in the browser the Angular does not intercept the URL to show me the content.
If I type the URL manually like: http://127.0.0.1:8080/#!/clients it works, but not when I type directly: http://127.0.0.1:8080/clients

I want the http://127.0.0.1:8080/#! as default in the http-server.

I'm using in AngularJS the html5 mode and the hash prefix:

$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');

Is there a way to rewrite the url from http-server with the default /#!/ before the address?

Upvotes: 2

Views: 2134

Answers (1)

twk
twk

Reputation: 1841

Note: Below is an example of a more complex Express.js URL rewriting situation, where you may not wish to "catch all" routes, but instead discern between routes for views and routes for the server-side api. All solutions I could find only showed a generic catch-all approach, which did not fit practical application for an app whom requires access to server-side routes. If you do simply want the "catch all", see the other yellow note at the bottom of this answer for links on how to set that up.

If you turn off html5 mode, by default, you should be in hashbang (#!) mode...Turning on html5Mode allows you to remove the hashbang (#!), if you wish.

Here's more about the different modes: https://docs.angularjs.org/guide/$location

HTML5 mode being enabled, gives you normal looking, non-hashbang URLs. Normally, when HTML5 mode is enabled, you'll hit the issue you described where pages within your app will load OK internally, but if you try and enter the URL directly into your browser (or access it via a bookmark), you'll get a Cannot GET error.

First, be sure you've set a <base> tag in the <head> your primary index file, like this:

<head>
    <!-- all your script tags, etc etc -->
    <base href="/">
    <!-- rest of your front-end dependencies etc -->
</head>

That way Angular will know which is your primary index to load partials within.

Secondly, I will try and tell you how I approached re-writing my URLs in Express to solve the issue you have described. My answer may be somewhat incomplete, as I am still learning, and in truth, I do not fully understand why, once HTML5 mode is enabled in Angular, that the routing does not work properly. There also may be better ways to approach the problem as opposed to how I solved mine.

It seemed that once I switched to HTML5 mode, Angular intercepted my routes and was causing an issue when I was trying to use the $http service to request server-side api routes.

It seemed like turning on HTML5 mode basically took over all of my routing, and I had to find a way to tell Express to either pass a route to Angular or to continue the route (away from angular) using next(). This was my best assessment.

What I Did:

  1. Enabled HTML5 mode [as you have done in your example], and set a <base> in my index file (as noted above).

  2. Rewrote my routing in ExpressJS using the native express.Router():

My Approach/Pseudo-code:

I setup a method in my Router so that if the incoming request contained /api/ (checked via regex), I would invoke ExpressJS's next() method and continue along in the route, which would hit the server controller. Otherwise, if the URL did not contain /api/, I the appropriate view page was delivered, in which Angular took over.

How I setup my express.Router():

  1. I created a middleware folder in my app and created a file called api-check.js.

    // Setup any Dependencies:
    var express = require('express'),
        router = express.Router(), // very important!
        path = require('path');
    
    // Setup Router Middleware:
        /*
            Notes: The `apiChecker` function below will run any time a request is made.
            The API Checker will do a regex comparison to see if the request URL contained
            the `/api/` pattern, to which instead of serving the HTML file for HTML5 mode,
            it will instead `next()` along so the API route can reach the appropriate
            server-side controller.
        */
    router.use(function apiChecker (req, res, next) {
        console.log('/// ROUTER RUNNING ///'); // will print on every route
        console.log('URL:', req.originalUrl); // will show requested URL
        var regex = /(\/api\/)/g; // pattern which checks for `/api/` in the URL
        if (regex.test(req.originalUrl)) { // if the URL contains the pattern, then `next()`
            console.log('THIS IS AN API REQUEST'); // api detected
            next();
        } else { // if the URL does not contain `/api`:
            res.sendFile(path.join(__dirname, './../../client/index.html'));     // delivers index.html which angular-route will then load appropriate partial
        }
    })
    
    module.exports = router; // exports router (which now has my apiChecked method attached to it)
    

How I added my express.Router() to my Express App:

Depending upon how your code is modularized, etc, just go ahead and in the right place require your module (you will have to adjust the direct path depending upon your project), and then app.use() the module to intercept all of your routes and direct them to your Router:

// Get Router:
var apiCheck = require('./../middleware/api-check');

// Use Router to intercept all routes:
app.use('/*', apiCheck); 

Now, any route (thus the /*) will go through the express.Router() apiChecker() function, and be assessed. If the requesting URL contains /api, then next() will be invoked and the server-side controller will be reached. Otherwise, if the /api slug is not detected in the URL, then the primary index.html base file will be sent, so that Angular can deliver the appropriate view via $routeProvider.

Note: If you don't need to discern between incoming routes, and just want to "catch all" incoming routes and hand back your <base> index file, you can do as outlined in another stackoverflow answer here. That answer uses app.get() to catch all GET requests to hand back your index. If you also need to catch POST requests or others, you may want to instead use app.all(), in place of app.get() in the aforementioned example. This will catch all routes, whether GET, POST, etc. Read more in the Express documentation here.

This was my personal solution, and there may be better, but this solved my problem! Would be interested to know what others recommend! Of course the downside to this, is that I have to build all of my internal api routes to include /api/ in them, however that seems to be OK in design overall, and maybe even useful in keeping me from confusing my routes from front-side views.

Hope this at least helps somewhat, let me know if you need any clarifications :)

Upvotes: 2

Related Questions