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!

 

Yeoman is a Javascript code generation and application bootstrapping framework that integrates many extremely cool, bleeding edge technologies for streamlining frontend rich Javascript application development.

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:

grunt server

And we’re off! The browser automatically launches on URL http://localhost:9000/ and displays our live-reloading application.

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:

http://localhost:9000/

Whereas my Django app is running on this address:

http://localhost:8000/

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!

Use the grunt-connect-proxy module and add a proxy to your gruntfile!

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!

Advertisements

16 thoughts on “Tip: Adding a local HTTP proxy when using Yeoman 1.0 with Grunt and Livereload

  1. Pingback: Grunt connect proxy to the rescue! | Rowboat Web

    • 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):

      1. /api/* -> Forwards requests to your backend application app
      2. /* -> 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)

    • Hi Mark! My earlier suggestion to Eddie related to the configuration of the server-side infrastructure supporting the Angular application running in the browser. The URL examples showcase HTTP server rules, not Angular routes.

  2. 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.

  3. 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..

    • Yes, exactly! Your AngularJS code represents a “standalone” app and should live separately from your server-side app providing data (via APIs) for that app.

      • 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.

        Ex:
        http://www.yourapp.com/api/* —> reverse proxy to your Django app
        http://www.yourapp.com/* —> serve static files from a local document root

    • 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.

      For CSRF, you can have a look here: https://docs.angularjs.org/api/ng/service/$http (look for XSRF in the page)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s