How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application

This post is now somewhat outdated. If you’re interested in Angular/Rails/Grunt/JavaScript/HTML, read on. If you’d prefer Angular/Rails/Gulp/CoffeeScript/Jade, check out this newer post.

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.

The sample app

There’s a certain sample app I plan to use throughout 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. I also created a branch specifically to match up with this tutorial 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. (I don’t have the client-side testing part 100% figured out yet.)

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. I suggest you just copy my file and replace yours wholesale.

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 Yeoman’s Angular generator. (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. Just accept all the defaults.

Start Grunt:

It’s likely that you’ll the error “invalid option: –fonts-dir”. The solution (or at least solution) to this problem is to remove the following line from your Gruntfile.js (line 186 for me):

When it spins up free of errors or warnings, Grunt should open a new browser tab for you at http://localhost:9000/#/ 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

We’ll use something called grunt-connect-proxy to forward certain requests from our Grunt server running on port 9000 to our Rails server running on port 3000.

Change your Gruntfile to match this:

Now kill Grunt and again run:

You should now be able to go to http://localhost:9000/api/groups and get empty brackets. (Make sure your Rails server is running.) Our Angular app is now talking to Rails.

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 we’ll add an AngularJS resource that will allow us to conveniently perform CRUD operations on Group. AngularJS resources match pretty nicely with Rails resources, and I’ve found that my code can be a lot more DRY using Angular resources than jQuery AJAX. We won’t get into the details here of the Angular/Rails resource interaction, though. All we’ll use is the query() method, which matches up to a Rails resource’s index action. Add the Group resource to app/scripts/app.js. I changed more than just a few lines in this file, so you might want to just copy and paste the whole thing.

Now add a controller for the Group resource:

And add a view.

Lastly, add the following line to app/index.html, near the bottom:

If you now go to http://localhost:9000/#/groups, you should see our list of groups.


I’ve written a separate article about deployment called How to Deploy an Angular/Rails Single-Page Application to Heroku.

This is a work in progress

This tutorial is a work in progress, and I intend to update it as a) the technology moves and b) I learn more. I’m aware that there are certain things missing, such as testing on the client side and end-to-end tests. But right now I just want to get this tutorial out into the world because I haven’t seen anything yet that spells out the Rails/Angular combo with this level of hand-holding. I hope this has been helpful. Please leave me any questions or comments you might have. Thanks.

65 thoughts on “How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application

  1. Steven Seagal says:

    Would you consider using a different syntax highlighting style or solution? The zebra striping is extremely distracting.

  2. Excellent write-up, but I’m running into problems on Mac OSX 10.9.4:

    I need to create a new PG user,

    $createuser -P -s -e fake_lunch_hub

    createuser: could not connect to database postgres: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

  3. Sasha says:

    In the RSpec configuration inside ‘config/application.rb’, I’d keep the requests and routing specs enabled over controller specs. If you’re writing an API consuming client-side application like Angular, then you care about whether routes exists and responses received from requests to those routes.

  4. Thanks!

    We’re in the middle of a transitory period. I convinced the CEO to let me turn first the purchasing pathway into an Angular app(so our entire dashboard up through trial exam and purchasing). We did that and now conversions are up because there is no page-reload time.

    However, I’m now stuck with a Rails 3.2 app that runs Angular some of the time and has separate pages at other times. Gradually I’m subsuming the old pages into the angular app but it’ll take months with our dev schedule. My end goal is to separate the angular app outside of the Rails app and this will be a great starting point for that once I’ve gotten all the views into Angular.

  5. Michael says:

    Following you tutorial, controller test can’t pass, because we must use strong parameter in Rails4, so we need add a private method to require group params.

  6. Andy says:

    Thanks for the write up.

    Could you please explain the reason to go with Grunt instead of just rely on rails’ asset pipeline?
    Seems like going through a bit of hoops to do that (e.g. proxy, 2 servers)

    • The reason is that I don’t know that you can do an actual single-page application when using AngularJS in the asset pipeline. I could be wrong about that. If I just wanted to use AngularJS as a library, and I weren’t worried about building an SPA, then I might have gone the asset pipeline route, and in fact I’ve done that before. The structure felt a little weird to me, though. Another reason for using Grunt, etc. is that I wanted to take advantage of the structure that Yeoman provides and I didn’t see how to do that inside the asset pipeline.

  7. evan says:

    rails api really removes protect_from_forgery from the application_controller? I thought the best practice was to set it to protect_from_forgery with: :null_session when just serving JSON.

    +1 for getting out of the asset pipeline. I’m switching over to this approach (but with gulp instead of grunt) and it seems nicer for a reusable api if you want to serve a mobile app or something else, too.

    The big hassle with this approach is authentication: from what I’ve read, APIs should be stateless, which rules out using rails’ sessions for authentication. You can use angular’s ngCookies to store tokens and whatnot on the client site, and then use an interceptor to configure authenticated requests, but there also seem to be a whole bunch of security issues with token auth. Lynn’s devise solution seems to be very well designed, but I haven’t played around with it much. I’ve been trying to roll my own auth to understand the pieces with angular and it’s a pain. Looking forward to seeing how you approach it.

    The other real cool thing about building outside of the asset pipeline is that it forces you structure your serializers well. I’m finding it avoids a lot of the n+1 gotchas that pop up in regular rails apps because you can’t query for more data in your views.

    good stuff

    • Thanks. The fact that you’re interested in how I approach the authentication part is motivating for me to get something out there. Next will be E2E testing, and then authentication is what I’m planning next (with E2E tests around it).

      • Cool, yeah the issue I ran into with separating angular from rails like this is that I had to manually reset the rails test db after protractor e2e test runs. If you embed angular in rails, there’s a nice gem that’ll handle that reset automatically, but I’m still looking for a good solution for when the rails and angular pieces are separate.

  8. Hey all,
    Noob here. I also got hung up on the Rspec controller failure:

    uninitialized constant GroupsController

    I’ve got the GitHub repo open but could use a hint. Thanks!

  9. Greg says:

    I’m using plain rails.
    I am getting cannot get /api/groups
    it’s accesible via plain url.
    grunt file is correctly configured I think
    Please help

  10. Vinney says:

    Just leaving this comment to say that I’m glad I found this site and I look forward to watching this post evolve and others appear :)

  11. It ate my HTML in my previous comment, sorry.

    You need to change your index.html to have an ng-view. <div ng-include=”‘views/main.html'” ng-controller=”MainCtrl”></div> should become <div ng-view ng-controller=”MainCtrl”></div> otherwise your routes won’t work.

  12. Anton says:

    Thank you for a great and clear tutorial, Jason! It’s what I tried to find.
    I have a question – why do you use ng-repeat on instead ?



    • I believe that’s just how ng-repeat works. I think of it as kind of being analogous to a for loop. If you mean that I made a syntactical mistake, then that’s a different thing, so let me know if it’s not working for you.

      • Anton says:

        It’s working, but when ng-repeat in ul-container there are a lot of ul containers and every of they contains one li element, but ul – container for li elements and right way of using ng-repeat on element you want repeat but nor on it’s parent. And in Angular docs all examples with ng-repeat point to li element.

  13. manoj says:

    I did as posted above but could not establish connection between api and angular, getting cannot GET /api/groups when i load localhost:9000/api/groups

      • Audrey says:

        Hey, thanks for this tutorial! I am also getting the same error, cannot GET api/groups when I load localhost. I do get a warning when I run grunt — Error: Cannot find module ‘simple-fmt’, but the server stars and runs without errors. I made sure my Gruntfile was identical to yours and I am using plain rails instead of rails-api. Any idea how I can fix this?

  14. Matthew Kilan says:

    Hi, this is very nice tutorial I have to admit.

    But personally I have a problem at the very end. When I copied your gruntfile.js, I got this error:

    Warning: ENOENT, no such file or directory ‘/home/mateusz/Desktop/Aptana_WorkSpace/fake_lunch_hub/app/bower.json’ Use –force to continue.

    So I opened gruntfile.js again and commented this section:

    wiredep: {
    options: {
    //cwd: ”

    And everything seemed to be fine. Then later when I did everything you did and wanted to see all data seeded to database I can see only this:


    from the Yeoman team

    Any suggestions?
    Thanks in advance.

  15. Matthew Kilan says:

    Problem solved.

    I have completely deleted “cwd” parameter in Gruntfile.js. Additionally I changed my routes.rb file.

    FakeLunchHub::Application.routes.draw do
    scope “/api” do
    resources :groups, except: [:new, :edit]

    And now everything works perfectly.
    Thanks for tutorial, amazing job! :)

  16. Mary says:

    Thank you so much for posting this! Has anyone had a problem with the group names from the seed data not showing up on the groups page? Mine is not, despite having run the rake db:seed, etc.


Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">