AngularJS + Martini: html5mode

By default AngularJS displays URL paths prefixed with a # symbol. This enables backwards compatibility with browsers that don’t support HTML5 history API. The AngularJS guide explains this in detail here

To remove the # symbol and display more normal-looking URLs requires the use of html5mode in AngularJS. This is enabled via $locationProvider, for example:

app.config(['$routeProvider', '$locationProvider',
       function($routeProvider, $locationProvider) {

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

           $routeProvider.
               when('/signin', {
               templateUrl: 'components/signin.html',
               controller: 'SigninCtrl'
           }). 
               when('/', {
               templateUrl: 'components/home.html',
               controller: 'HomeCtrl'
           }). 
               otherwise({
               redirectTo: '/' 
           }); 
       }   
]);

If the client starts browsing at / then this will work fine. The webserver sends index.html to the client and AngularJS can load itself up ready to handle the other routes itself. A problem occurs if the client starts browsing directly to a path that the webserver may not know about, for example /signin. When the web server receives that request it will return a HTTP 404 error. What we need to do instead is tell our server to serve the contents of index.html. Note, we are not performing a HTTP redirect here.

I’m using Go to serve my static content, and using the Martini web framework to make life a bit easier.

To have Martini to do the necessary rewrite, perform the following setup:

		router := martini.NewRouter()
        router.NotFound(func(w http.ResponseWriter, r *http.Request) {
                // Only rewrite paths *not* containing filenames
                if path.Ext(r.URL.Path) == "" {
                        http.ServeFile(w, r, "public/index.html")
                } else {
                        w.WriteHeader(http.StatusNotFound)
                        w.Write([]byte("404 page not found"))
                }
        })
        m := martini.New()
        m.Use(martini.Logger())
        m.Use(martini.Recovery())
        m.Use(martini.Static("public"))
        m.Action(router.Handle)

Change public to suit whichever folder your web content lives in.