Tip: Adding a local HTTP proxy when using Yeoman 1.0 with Grunt and Livereload

[UPDATE]

Grunt and the plugins ecosystem is moving quite fast, and thus this post is a bit outdated now. Although the concepts still apply, Livereload is now embedded in the “watch” plugin and the way to configure the “connect-proxy” plugin to achieve a local proxy to your Web app has changed quite a bit. Documentation has also improved so kudos to the Grunt community!

We just started an Angular.js application at work and we used Yeoman to kickstart the application’s structure. Not only did the initial project generation work (almost) flawlessly, but we discovered some truly amazing new tools like Livereload (which really *is* the web developer wonderland!) and Compass.

Developing a rich client-side only Javascript with Yeoman’s tooling requires the developer to run a simple local HTTP server to be able to see the application running. This HTTP server (Livereload) is out of this world: it will reload the app (including all code, SASS/CSS generation, images, etc.) on-the-fly upon any change in the source code. And by reload I don’t mean the usual you refresh the page in your browser, I mean Livereload will refresh everything automatically as soon as you hit Save on any source file of your project.

When we want to run our Angular.js application with Yeoman, all we do is run this command:

This is all extremely good stuff, but we quickly hit a road block. Since our application is being served by a local server that serves only static files, how do we interact with our backend server (a Django application in our case) given most browsers prevent cross-site scripting (XSS)?

More specifically, the local server running my Angular.js application is accessible on this address:

If I try to do any remote Ajax call to my backend server application (the one that runs on port 8000), it will fail because my browser will refuse to run an XHR to a different origin. The solution most JS frameworks come with is to add a simple HTTP proxy feature within their local server to deal with the same-origin restrictions imposed by browsers.

Unfortunately, even if this use case is almost always encountered when developing a rich JS app locally, Yeoman’s documentation on that use case is very scarce. So save yourself the search, here’s the trick!

Just diligently follow the procedure on the project’s GitHub page and it’ll work. There is no mention anywhere that this works with Yeoman’s Grunt setup, but it does!

After you install the module and apply the few changes to your project, your Grunt server will proxy requests to your backend application running locally. When running the “grunt server” command, you should see this:

...
Running "configureProxies" task
Proxy created for: /api
...

Which confirms your proxy is properly setup.
Just make sure your backend application’s URLs are all prefixed by a specific path (ex: /api is often used) and you’ll be good to go!

On the actual server, you front your application by an HTTP server (Apache, Nginx, etc.) acting as a reverse proxy to your backend application and serving your static JS app with the following configuration (or variations thereof):

/api/* -> Forwards requests to your backend application app

/* -> Serves your static Javascript application

Knowing that, all you need is to prefix all URLs used to access your backend with /api (or whatever prefix you choose) and that’s pretty much it!

The problem with JSONP is that it’ll require tricks with Javascript frameworks such as jQuery to use any other HTTP methods than GET. If you want to design your backend API around RESTful principles, you need most of the other HTTP verbs available. Not to say that it cannot be done, but the solution above (with the proxy) is definitely more natural. Having everything on the exact same domain just plays nice with our users browsers, which are pretty strict about XSS (Cross-Site Scripting)

I followed the instructions quite diligently, but when I try to launch grunt (which is now ‘grunt serve’), I get an error saying, ‘Warning: Task “livereload-start” not found.’ If I disable that line in the task, the server will kick up, but the page won’t load. I’m kind of stumped right now 😦

I have added a note at the beginning of the article to raise a few changes I noted when quickly revisiting the involved plugins. I haven’t tested this recently but the links I provided should get you to a good start.

So are you suggesting that (for local dev) we completely separate the Angularjs app from the Django project? Meaning that it will not be inside the Django root path and it won’t be served by Django? That could make sense, since I’ve been struggling making django serving all angular files and paths..

I see, thank you, I was getting so many headaches…but who is gonna serve the main (and maybe unique) html pages containing all the references to angular? Normally it supposed to be Django to compose and serve and index page..I’m a bit confused. Do you have any example on github?

Yes, that’s the traditional way to do it. But really there’s no need for that with Angular. I don’t have an example but you can start with a basic Yeoman bootstrapped app (http://yeoman.io/learning/index.html) to see how this fits in.

Django won’t be used to generate any of the static content for your app (HTML and all), but will only be used to serve a REST API for your Angular app.
Your static site (the AngularJS app) is to be served by any static files Web server (Nginx, Apache, etc.) you like. Because of cross-domain scripting limitations, you need to have your static files hosted on the exact same domain as your Django backend app.

A common setup is to configure Nginx or Apache as a frontend reverse proxy, onto which you host your AngularJS static files (as a standard document root), and also configure a reverse-proxy to your Django app on the same.

Sessions can be managed via your Django-provided API but it’s obviously more manual. Remember, it’s still the browser that makes requests to your Django backend, so you can still use cookies transparently. You can have an authentication API endpoint that will set you up with a session cookie just as you would do with a traditional HTTP request.