This tutorial is out of date and no longer maintained.
Note: Part one of a three-part series.
Build a RESTful JSON API With Rails 5 - Part One Build a RESTful JSON API With Rails 5 - Part Two Build a RESTful JSON API With Rails 5 - Part Three
Rails is popularly known for building web applications. Chances are if you’re reading this you’ve built a traditional server-rendered web application with Rails before. If not, I’d highly recommend going through the Getting Started with Rails page to familiarize yourself with the Rails framework before proceeding with this tutorial.
As of version 5, Rails core now supports API-only applications! In previous versions, we relied on an external gem: rails-api which has since been merged to core rails.
API-only applications are slimmed down compared to traditional Rails web applications. According to Rails 5 release notes, generating an API only application will:
ApplicationController
inherit from ActionController::API
instead of ActionController::Base
This works to generate an API-centric framework excluding functionality that would otherwise be unused and unnecessary.
In this three-part tutorial, we’ll build a todo list API where users can manage their to-do lists and todo items.
Before we begin, make sure you have ruby version >=2.2.2 and rails version 5.
If your ruby version is not up to date, you can update it with a ruby version manager like rvm or rbenv.
If your rails version is not up to date, update to the latest version by running:
All good? Let’s get started!
Our API will expose the following RESTful endpoints.
Endpoint | Functionality |
---|---|
POST /signup | Signup |
POST /auth/login | Login |
GET /auth/logout | Logout |
GET /todos | List all todos |
POST /todos | Create a new todo |
GET /todos/:id | Get a todo |
PUT /todos/:id | Update a todo |
DELETE /todos/:id | Delete a todo and its items |
GET /todos/:id/items | Get a todo item |
PUT /todos/:id/items | Update a todo item |
DELETE /todos/:id/items | Delete a todo item |
Part One will Cover:
Generate a new project todos-api
by running:
Note that we’re using the --api
argument to tell Rails that we want an API application and -T
to exclude Minitest the default
testing framework. Don’t freak out, we’re going to write tests. We’ll be using RSpec instead to test our API. I find RSpec to be more expressive and easier to start with as compared to Minitest.
Let’s take a moment to review the gems that we’ll be using.
All good? Great! Let’s set them up. In your Gemfile
:
Add rspec-rails
to both the :development
and :test
groups.
This is a handy shorthand to include a gem in multiple environments.
Add factory_bot_rails
, shoulda_matchers
, faker
and database_cleaner
to the :test
group.
Install the gems by running:
Initialize the spec
directory (where our tests will reside).
This adds the following files which are used for configuration:
.rspec
spec/spec_helper.rb
spec/rails_helper.rb
Create a factories
directory (factory bot uses this as the default directory). This is where we’ll define the model factories.
In spec/rails_helper.rb
Phew! That was rather long. Good thing is, it’s a smooth ride from here on out.
Let’s start by generating the Todo
model
Notice that we’ve included the model attributes in the model generation command. This way we don’t have to edit the migration file.
The generator invokes active record
and rspec
to generate the migration, model, and spec respectively.
And now the Item
model
By adding todo:references
we’re telling the generator to set up an association with the Todo
model.
This will do the following:
todo_id
to the items
tablebelongs_to
association in the Item modelLooks good? Let’s run the migrations.
We’re Test Driven, let’s write the model specs first.
RSpec has a very expressive DSL (Domain Specific Language). You can almost read the tests like a paragraph.
Remember our shoulda matchers gem? It provides RSpec with the nifty association and validation matchers above.
Let’s execute the specs by running:
And to no surprise, we have only one test passing and four failures. Let’s go ahead and fix the failures.
At this point run the tests again and…
Voila! All green.
Now that our models are all set up, let’s generate the controllers.
You guessed it! Tests first… with a slight twist. Generating controllers by default generates controller specs.
However, we won’t be writing any controller specs. We’re going to write request specs instead.
Request specs are designed to drive behavior through the full stack, including routing. This means they can hit the applications’ HTTP endpoints as opposed to controller specs which call methods directly. Since we’re building an API application, this is exactly the kind of behavior we want from our tests.
According to RSpec, the official recommendation of the Rails team and the RSpec core team is to write request specs instead.
Add a requests
folder to the spec
directory with the corresponding spec files.
Before we define the request specs, Let’s add the model factories which will provide the test data.
Add the factory files:
Define the factories.
By wrapping Faker methods in a block, we ensure that Faker generates dynamic data every time the factory is invoked.
This way, we always have unique data.
We start by populating the database with a list of 10 todo records (thanks to factory bot). We also have a custom helper method json
which parses the JSON response to a Ruby Hash which is easier to work with in our tests.
Let’s define it in spec/support/request_spec_helper
.
Add the directory and file:
The support directory is not autoloaded by default. To enable this, open the rails helper and comment out the support directory auto-loading and then include it as shared module for all request specs in the RSpec configuration block.
Run the tests.
We get failing routing errors. This is because we haven’t defined the routes yet. Go ahead and define them in config/routes.rb
.
In our route definition, we’re creating todo resource with a nested items resource. This enforces the 1:m (one to many) associations at the routing level. To view the routes, you can run:
When we run the tests we see that the routing error is gone. As expected we have controller failures. Let’s go ahead and define the controller methods.
More helpers. Yay! This time we have:
json_response
which does… yes, responds with JSON and an HTTP status code (200 by default).
We can define this method in concerns folder.set_todo
- callback method to find a todo by id. In the case where the record does not exist, ActiveRecord will throw an exception ActiveRecord::RecordNotFound
. We’ll rescue from this exception and return a 404
message.In our create
method in the TodosController
, note that we’re using create!
instead of create
. This way, the model will raise an exception ActiveRecord::RecordInvalid
. This way, we can avoid deep nested if statements in the controller. Thus, we rescue from this exception in the ExceptionHandler
module.
However, our controller classes don’t know about these helpers yet. Let’s fix that by including these modules in the application controller.
Run the tests and everything’s all green!
Let’s fire up the server for some good old manual testing.
Now let’s go ahead and make requests to the API. I’ll be using httpie as my HTTP client.
You should see similar output.
As expected, running the tests at this point should output failing todo item tests. Let’s define the todo items controller.
Run the tests.
Run some manual tests for the todo items API:
That’s it for part one! At this point you should have learned how to:
RSpec
testing framework with Factory Bot
, Database Cleaner
, Shoulda Matchers
, and Faker
.In the next part, we’ll cover authentication
with JWT, pagination
, and API versioning
. Hope to see you there. Cheers!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Excellent article, the entire series actually!
There’s one small error in the command for Faker, it should be
Faker::Movies::StarWars.character
instead ofFaker::StarWars.character
Nice tutorial, I can’t wait for the second part
While using Rails 6, adding gem ‘rubocop-faker’ gem ‘rubocop-rspec’, require: false and running rubocop --autocorrect help fixed some syntax that was not compatible with rails 6. Great tutorial, keep it coming.
This was a really well-written article and helpful. Thanks and keep it coming!
I am facing this error
NameError: uninitialized constant Shoulda
I am running Rails 6.0.3.4
can someone help me with this issue?
I was getting an error:
It seems to be a typo, at:
The “RSpec.configuration” should be “RSpec.configure”.
I had to add these lines to the spec/spec_helper.rb file:
require ‘database_cleaner’ require ‘shoulda/matchers’ require ‘factory_bot_rails’ require ‘action_controller’
To avoid getting a lot of errors like these:
when running the tests.
Which by the way I ran them by using the command:
$ rspec
Is there a risk of running test with that command instead of:
$ bundle exec rspec
?
In the ‘create’ action of the item controller.
If you make it this way:
After you create an item, the API will return the Todo of the Item. But if you make it this way:
You will get the Item after creating the item with the id in the database.
Excellent walkthrough, thanks for writing it up. A couple of easy tweaks can be edited in the factory file examples so the current version of Faker doesn’t pile on the depreciation messages upon running RSpec:
thank u very much! This was really helpful