Angular 1 Gulp Rails Single-Page Applications

How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application (Gulp Version)

Important note: This tutorial no longer works if followed verbatim. I would recommend my more recent updated-for-2016 version.

Why this tutorial exists

I wrote this tutorial because I had a pretty tough time getting Rails and Angular to talk to each other as an SPA. The best resource I could find out there was Ari Lerner’s Riding Rails with AngularJS. I did find that book very helpful and I thought it was really well-done, but it seems to be a little bit out-of-date by now and I couldn’t just plug in its code and have everything work. I had to do a lot of extra Googling and head-scratching to get all the way there. This tutorial is meant to be a supplement to Ari’s book, not a replacement for it. I definitely recommend buying the book because it really is very helpful.

Refreshed for 2015

I had written a tutorial with almost the same title last summer. In that tutorial I used Grunt instead of Gulp, HTML instead of HAML or Jade, and regular JavaScript instead of CoffeeScript or ES6. Not only do cooler alternatives to those traditional technologies exist today, they did back then, too. My tutorial was sorely in need of a reboot. So here it is.

The sample app

There’s a certain sample app I plan to use throughout AngularOnRails.com called Lunch Hub. The idea with Lunch Hub is that office workers can announce in the AM where they’d like to go for lunch rather than deciding as they gather around the door and waste half their lunch break. Since Lunch Hub is a real project with its own actual production code, I use a different project here called “Fake Lunch Hub.” You can see the Fake Lunch Hub repo here.

Setting up our Rails project

Instead of regular Rails we’re going to use Rails::API. I’ve tried to do Angular projects with full-blown Rails, but I end up with a bunch of unused views, which feels weird. First, if you haven’t already, install Rails::API.

Creating a new Rails::API project works the same as creating a regular Rails project.

Get into our project directory.

Create our PostgreSQL user.

Create the database.

Now we’ll create a resource so we have something to look at through our AngularJS app. (This might be a good time to commit this project to version control.)

Creating our first resource

Add gem 'rspec-rails' to your Gemfile (in the test group) and run:

When you generate scaffolds from now on, RSpec will want to create all kinds of spec files for you automatically, including some kinds of specs (like view specs) that in my opinion are kind of nutty and really shouldn’t be there. We can tell RSpec not to create these spec files:

(Now might be another good time to make a commit.)

In Lunch Hub, I want everybody’s lunch announcements to be visible only to other people in the office where they work, not the whole world. And there’s actually a good chance a person might want to belong not only to a group tied to his or her current workplace, but perhaps a former workplace or totally arbitrary group of friends. So I decided to create the concept of a Group in Lunch Hub. Let’s create a Group resource that, for simplicity, has only one attribute: name.

Since groups have to have names, let’s set null: false in the migration. We’ll also include a uniqueness index.

Now, if you run rails server and go to http://localhost:3000/groups, you should see empty brackets ([]). We actually want to be able to do http://localhost:3000/api/groups instead.

At the risk of being annoying, I wanted to include a realistic level of testing in the tutorial, at least on the server side.

To make this spec pass you’ll of course need to add a validation:

We also have to adjust the controller spec RSpec spit out for us because RSpec’s generators are evidently not yet fully compatible with Rails::API. The generated spec contains an example for the new action, even though we don’t have a new action. You can remove that example yourself or you can just copy and paste my whole file.

Now if you run all specs on the command line ($ rspec), they should all pass. We don’t have anything interesting to look at yet but our Rails API is now good to go.

Adding the client side

On the client side we’ll be using Yeoman, a front-end scaffolding tool. First, install Yeoman itself as well as generator-gulp-angular. (If you don’t already have npm installed, you’ll need to do that. If you’re using Mac OS with Homebrew, run brew install npm.)

We’ll keep our client-side code in a directory called client. (This is an arbitrary naming choice and you could call it anything.)

Now we’ll generate the Angular app itself. When I ran it, I made the following selections:

Free Guide

Getting Started with Angular and Rails

Get an Angular/Rails app up and running in as little as 20 minutes

  • Angular version: 1.3.x
  • Modules: all
  • jQuery: 2.x
  • REST resource library: ngResource (just because it’s the default and angularjs-rails-resource isn’t an option on the list)
  • Router: UI Router
  • UI framework: Bootstrap
  • Bootstrap component implementation: Angular UI
  • CSS preprocessor: Sass (Node)
  • JS preprocessor: CoffeeScript
  • HTML template engine: Jade

If you have a Rails server running on port 3000, stop it for now because Gulp will also run on port 3000 by default. Start Gulp to see if it works:

Gulp should now open a new browser tab for you at http://localhost:3000/#/ where you see the “‘Allo, ‘Allo” thing. Our Angular app is now in place. It still doesn’t know how to talk to Rails, so we still have to make that part work.

Setting up a proxy

The only way our front-end app (Angular and friends) will know about our back-end server (Rails) is if we tell our front-end app about our back-end app. The basic idea is that we want to tell our front-end app to send any requests to http://our-front-end-app/api/whatever to http://our-rails-server/api/whatever. Let’s do that now.

If you look inside client/gulp, you’ll notice there’s a file in there called proxy.js. I would like to have simply tweaked this file slightly to get our proxy working, but unfortunately I found proxy.js very confusing and difficult to work with. So I deleted it and set up the proxy a different way. Let’s delete proxy.js so it doesn’t confuse future maintainers.

You’ll notice another file inside client/gulp called server.js. I found that minimal adjustment in this file was necessary in order to get the proxy working. Here’s what my server.js looks like after my modifications, which I’ll explain:

Here are the things I changed, it no particular order:

  1. Configured BrowserSync to run on port 9000 so Rails can run on its default port of 3000 without conflicts
  2. Added middleware that says “send requests to /api to http://localhost:3000
  3. Added a rails task that simply invokes the rails server command
  4. Added a serve:full-stack task that runs the regular old serve task, but first runs the rails task

You’ll have to install http-proxy-middleware before continuing:

Now we can run our cool new task. Make sure neither Rails nor Gulp is already running somewhere.

Three things should now happen:

  1. The front-end server should come up on port 9000 instead of 3000.
  2. If you navigate to http://localhost:9000/api/foo, you should get a Rails page that says No route matches [GET] "/api/foo", which means
  3. Rails is running on port 3000.

Getting Rails data to show up in our client app

Now we’ll want to get some actual data to show up in the actual HTML of our Angular app. This is a pretty easy step now that we have the plumbing taken care of. First, create some seed data:

Get the data into the database:

Now let’s modify src/app/index.coffee to include a state for groups:

Then we add GroupsCtrl, which at this point is almost nothing:

(I manually created a new directory for this, src/app/controllers.)

Lastly, let’s create a view at src/app/views/groups.jade:

If you now navigate to http://localhost:9000/#/groups, you should see a big h1 that says “Groups”. So far we’re not talking to Rails at all yet. That’s the very next step.

A good library for Angular/Rails resources is called, straightforwardly, angularjs-rails-resource. It can be installed thusly:

Now let’s add two things to src/app/index.coffee: the rails module and a resource called Group.

Now let’s add a line to our controller to make the HTTP request:

And some code in our template to show the group names:

If you now visit http://localhost:9000/#/groups, you should see your group names there. Congratulations! You just wrote a single-page application. It’s a trivial and useless single-page application, but you’re off to a good start. In my experience the plumbing is the hardest part.

That’s all for now

I’ve heard requests for tutorials on basic CRUD operations in Angular/Rails, so keep your eye out for that in the near future. You can subscribe to my posts by leaving your email in the upper right corner.

Also, if you enjoyed this tutorial, you may also like my webinar version of this same tutorial (and then some). Thanks for reading.

About the author

Jason Swett

37 Comments

Click here to post a comment

  • Thanks for the nice article, looking for some simple crud operations with angular. I also want to understand can we create a whole app with angular only?

    • Thanks, Ananth. I plan to write an article about CRUD operations in the near future. To answer your question, yes, you can create a whole app with just Angular, although it may be limited in capability. Most apps require the ability to persist data and I don’t know how you’d do that without some sort of back-end. There exist certain data-store-as-a-service products, e.g. Firebase, but I prefer a Rails API as the back-end.

    • Hey, thanks so much for this! I would so appreciate your help if you have a minute. If not I still appreciate this tutorial!

      All was going well until I got this Error going to http://localhost:9000/api/foo:

      Proxy error: ECONNREFUSED. localhost:9000 localhost:3000/

      I am trying to keep my Rails app and Angular app each in their own repo, so I need to address CORS issues

      I added the rack-cors gem and this in initializers/cors.rb:

      Rails.application.config.middleware.insert_before 0, “Rack::Cors” do
      allow do
      origins ‘localhost:9000’

      resource ‘*’,
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
      end
      end

      Makes sense. But I’m still getting the same

      Error occured while trying to proxy to: localhost:9000/api/foo

      It is a 500 error, which suggests to me I’m still running into CORS problems.

      Any advice? Thanks so much.

      • Did the whole thing over again with gulp nested in the rails app and everything goes smooth until I run into this again:

        Error occured while trying to proxy to: localhost:9000/api/foo

        Please help!

        • Excellent tutorial. I’ve spent time with this today and am getting exactly the same error as Alex:

          [HPM] Proxy error: ECONNREFUSED, localhost:9000 -> “localhost:3000/api/foo”

          When I go to localhost:3000/api/groups with the browser the proper JSON response from Rails is rendered.

          I see the following in my logging and can only assume the proxy is set up properly:

          [HPM] Proxy created: /api -> http://localhost:3000/api

          I’ve searched the internet for several hours trying various things, but nothing has been successful so far. Any insight anyone could provide would be much appreciated.

          Thanks!
          Sam

  • Thanks man! This is by far the best tutorial I can find on getting started with angular on rails. I’ll be keeping my eye out for CRUD operations.

  • My team started developing our Angular Rails app in a similar fashion (we used Grunt instead of Gulp). We got to a point where managing 2 separate stacks (Rails and NodeJS) became unmanageable.

    We ended up integrating everything directly into the Rails application. We use Bundler and Rails-Assets.org in place of Bower and Sprockets and the Asset Pipeline in place of Grunt. We created a AngularController and routing wildcards to serve the view templates for the Angular states.

    We cut our deployment times by 75% and drastically decreased deployment errors by taking this course.

    • Hi, i’m with the same problems, managing 2 separate projects is being a really pain! Do you have some tutorials or tips to change for your approach? Anything will help. Thanks

  • Hmm. Seems a bit complicated. I have regular rails app running with angular by just serving data through a JSON api. How did you end up with so many unused views when you tried this? I’m just using templates on the front end.

  • Very well done Jason. This is a just-right follow-up for reading Ari’s book which I also highly recommend. You saved me several hours of research and experimentation.

  • Thanks for this nice tutorial. Agree to Jason..why not just use JSON Api inside Rails.
    But a second question: Why coupling one framework with another? Why routing with Angular if Rails has a perfect routing for example?
    I just came across ‘RailsMeetsReact.JS’ .. sounds interesting. To me looks more fitting.

  • I agree with Tim. In my opinion in the long run this kind of setup is difficult to maintain and the power of rails is not fully used with this kind of setup. Rails is just used for API server. I much more gear to this http://angular-rails.com/index.html kind of implementation. My 2 cents idea 🙂 But nice blog!

  • This the ONE blog post, of so many out there in the wilderness, that made sense for me.
    Thanks for the write up.
    I would like to know how would you integrate an authentication system and deploy (to heroku). An authentication system (perhaps devise) integration and deployment strategy would make this post complete 🙂 I would buy it if you made a book out of these, describing a full-stack rails angular app development.
    Keep up the good work.

  • Great, detailed walkthrough! I had never used Gulp, so the generator-gulp-angular introduced me to some cool techniques and tooling there. Thanks for sharing.

    A couple small suggestions to help future readers:

    It appears that http-proxy-middleware is now included automatically by generator-gulp-angular (but you still have to configure it in server.js as you do in this walkthrough), so I believe you can remove these two lines (and their text sections) from the tut:

    rm gulp/proxy.js
    npm install --save-dev http-proxy-middleware

    Also, since rails is sitting in the server subfolder of the project alongside angular in the client folder, I don’t think your gulp task to run rails works as it should, because it assumes that rails is installed in the same folder as gulp. I believe this line should be changed to:


    gulp.task('rails', function() {
    exec("cd ../server && rails server");
    });

    Thanks again for the sweet tut!

  • Very nice tutorial! Thank you very much!

    At the end of the tutorial when I visit: http://localhost:9000/#/groups I can see that my browser is sending a GET request to: http://localhost:9000/api/groups.json which is not working because my rails app is running on http://localhost:3000

    Did I miss something?

  • I was excited about this tutorial however I have spent a while now fumbling with an error.

    I get to the point where the server.js file is replaced with the new gulp task “full-stack” as well as other code replacements. After this all gulp tasks are not recognized. The errors I get are:

    Task ‘serve’ is not in your gulpfile
    Task ‘serve:full-stack’ is not in your gulpfile

    Wondering if anyone has had the same issue and could advise. Really excited about finishing this and having it work. Thanks for taking the time to do this post

      • Figured it out. You have to go in your server.js file and do the following, which worked for me:

        ‘use strict’;

        var path = require(‘path’);
        var gulp = require(‘gulp’);
        var conf = require(‘./conf’);

        var browserSync = require(‘browser-sync’);
        var browserSyncSpa = require(‘browser-sync-spa’);

        var util = require(‘util’);

        var exec = require(‘child_process’).exec;

        var proxyMiddleware = require(‘http-proxy-middleware’);

        function browserSyncInit(baseDir, browser) {
        browser = browser === undefined ? ‘default’ : browser;

        var routes = null;
        if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
        routes = {
        ‘/bower_components’: ‘bower_components’
        };
        }

        var server = {
        baseDir: baseDir,
        routes: routes,
        middleware: [
        proxyMiddleware(‘/api’, { target: ‘http://localhost:3000’ })
        ]
        };

        /*
        * You can add a proxy to your backend by uncommenting the line below.
        * You just have to configure a context which will we redirected and the target url.
        * Example: $http.get(‘/users’) requests will be automatically proxified.
        *
        * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.9.0/README.md
        */
        // server.middleware = proxyMiddleware(‘/users’, {target: ‘http://jsonplaceholder.typicode.com’, changeOrigin: true});

        browserSync.instance = browserSync.init({
        port: 9000,
        startPath: ‘/’,
        server: server,
        browser: browser
        });
        }

        browserSync.use(browserSyncSpa({
        selector: ‘[ng-app]’// Only needed for angular apps
        }));

        gulp.task(‘serve’, [‘watch’], function () {
        browserSyncInit([path.join(conf.paths.tmp, ‘/serve’), conf.paths.src]);
        });

        gulp.task(‘rails’, function() {
        exec(“rails server”);
        });

        gulp.task(‘serve:full-stack’, [‘rails’, ‘serve’]);

        gulp.task(‘serve:dist’, [‘build’], function () {
        browserSyncInit(conf.paths.dist);
        });

        gulp.task(‘serve:e2e’, [‘inject’], function () {
        browserSyncInit([conf.paths.tmp + ‘/serve’, conf.paths.src], []);
        });

        gulp.task(‘serve:e2e-dist’, [‘build’], function () {
        browserSyncInit(conf.paths.dist, []);
        });

  • Thanks for great introduction but it looks like at last section it gets messy:
    * No src/app/index.coffee file found. Instead I have several index.*.config files.
    * Where does GroupsCtrl go?

    • Hello! One thing which prevented from completing this tutorial was missing “rails” in the list of dependencies. Now all works fine.

Free Guide: Getting Started with Angular and Rails

Learn More