2014-01-05

Grunt Proxy Setup for Yeoman

I'm working on a webapp using Angular.js with the help of the Yeoman workflow framework. Because the backend service runs on localhost:8080 and my dev server (through Grunt) on localhost:9000, requests result in same-origin policy errors due to the port difference. To get around this during development, we can setup CORS through a proxy on Grunt.

Much of this post is adapted from the grunt-connect-proxy documentation for my project's Yeoman-created Grunt.js.

Tools used:
  • yo (Yeoman) 1.0.7-pre.2
  • generator-angular 0.7.1
  • grunt-cli v0.1.11
  • grunt v0.4.2
  • grunt-connect-proxy 0.1.7

I started with a working Angular webapp with Yeoman and friends, so check out Yeoman and generator-angular to get up to speed.
  1. Install grunt-connect-proxy in the root of your project folder. The --save-dev parameter tells NPM to add grunt-connect-proxy as a devDependency to your project's package.json.
    npm install grunt-connect-proxy --save-dev
    NOTE: The grunt-connect-proxy documentation states that you can enable it by adding the following line to your Gruntfile.js:
    grunt.loadNpmTasks('grunt-connect-proxy');
    When I added it, Grunt registered the task twice and while this didn't appear to cause any problems, this is the reason that I leave it out of my Gruntfile.js.
  2. Add a proxies config to your connect config. I added it just before livereload because we'll be modifying that config in the next step.
    // The actual grunt server settings
    connect: {
      options: {
        // content removed for brevity
      },
      proxies: [{
        context: '/data-service-path', // the context of the data service
        host: 'localhost', // wherever the data service is running
        port: 8080 // the port that the data service is running on
      }],
      livereload: {
        // content removed for brevity
      },
      test: {
    
    Check out the grunt-connect-proxy documentation for details on proxy configuration.
  3. Add the grunt-connect-proxy proxyRequest function to middleware in connect.livereload.options. This causes all requests to the Grunt server for the path of the configured context to be proxied; I'll provide some examples at the end of the post on how URLs are mapped by default. Here's what we end up with:
    // The actual grunt server settings
    connect: {
      options: {
        // content removed for brevity
      },
      proxies: [{
        context: '/data-service-path', // the context of the data service
        host: 'localhost', // wherever the data service is running
        port: 8080 // the port that the data service is running on
      }],
      livereload: {
        options: {
          open: true,
          base: [
            '.tmp',
            '<%= yeoman.app %>'
          ],
          middleware: function (connect, options) {
            var middlewares = [];
              
            if (!Array.isArray(options.base)) {
              options.base = [options.base];
            }
              
            // Setup the proxy
            middlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest);
    
            // Serve static files
            options.base.forEach(function(base) {
              middlewares.push(connect.static(base));
            });
    
            return middlewares;
          }
        }
      },
    
    This is different from the example given in the grunt-connect-proxy documentation in that my version doesn't make anything browseable.
  4. Add the configureProxy task to the Grunt serve task. Here's what my serve task ends up as:
    grunt.registerTask('serve', function (target) {
      if (target === 'dist') {
        return grunt.task.run(['build', 'connect:dist:keepalive']);
      }
    
      grunt.task.run([
        'clean:server',
        'bower-install',
        'concurrent:server',
        'autoprefixer',
        'configureProxies:server', // added just before connect
        'connect:livereload',
        'watch'
      ]);
    });
    
  5. Start up the Grunt server with
    grunt serve
    and you should see
    Running "configureProxies:server" (configureProxies) task
    Proxy created for: /data-service-path to localhost:8080
    
    in the console.
That's all for installation and configuration.

Here are some example proxied URLs:

Grunt serverBackend server
http://127.0.0.1:9000/data-service-pathhttp://127.0.0.1:8080/data-service-path
http://127.0.0.1:9000/data-service-path/foo/1http://127.0.0.1:8080/data-service-path/foo/1

For an Angular resource whose endpoint is over the proxy then, the proxy context must be used in the resource URL, something like this:
angular.module('myServices', ['ngResource'])
.factory('Foo', ['$resource', function($resource) {
    return $resource('/data-service-path/foo/:id', {id: '@id'});
}]);
To use a different path on the Grunt and backend servers, use the rewrite config; for example:
// The actual grunt server settings
connect: {
  options: {
    // content removed for brevity
  },
  proxies: [{
    context: '/api', // the path your application uses
    host: 'localhost', // wherever the data service is running
    port: 8080, // the port that the data service is running on
    rewrite: {
        // the key '^/api' is a regex for the path to be rewritten
        // the value is the context of the data service
        '^/api': '/data-service-path'
    }
  }],
  livereload: {
    // content removed for brevity
  },
  test: {
Example URLs:

Grunt serverBackend server
http://127.0.0.1:9000/apihttp://127.0.0.1:8080/data-service-path
http://127.0.0.1:9000/api/foo/1http://127.0.0.1:8080/data-service-path/foo/1

Then our example resource code becomes:
angular.module('myServices', ['ngResource'])
.factory('Foo', ['$resource', function($resource) {
    return $resource('/api/foo/:id', {id: '@id'});
}]);

3 comments:

  1. Thanks for the helpful configuration walkthrough. After trying to read through the official docs for grunt-connect-proxy, your post was what finally made it all make sense. :)

    ReplyDelete
  2. After following the walkthrough I do have grunt telling me that the proxy is working. Upon running grunt serve I see the message:

    Running "configureProxies:server" (configureProxies) task
    Proxy created for: /api to localhost:8080

    However when I try to make a call using either $http or $resource I can see through the network and console that the call is still going to localhost:9000 which is where my node server is running the angular app. This is a gist of my gruntfile. Any input would be much appreciated.

    https://gist.github.com/JohnBueno/7d48027f739cc91e0b79

    ReplyDelete
  3. @John - your proxies config is inside connect.server, but in my Gruntfile it's a child of connect, at the same level as livereload.
    The post incorrectly states that proxies should be added to connect.server, but I'll fix that shortly.

    ReplyDelete