Building API in Rails - Part 2

Creating our first API endpoint

ruby on rails API

Let’s create our first RESTful API endpoint which will be a CRUD endpoint for the user. Before doing that we need to make sure where our API code should go. I hope you are familiar with the rails MVC pattern and how Rails organizes its codebase. If not go ahead and check this out first.

API versioning

Initially, the version of our API maybe let’s say ‘V1’ and as our application grows it will have many versions for the API as well(v1, v2, v3….). The clients that use our API may not only use the latest version, what I mean by that is there may be some clients which may only use certain versions of the API let’s say v1 even though our latest version is v3. So we must keep in mind the versioning of our API.

Inside the controller folder of our app let’s make a new folder as api and inside the api folder let’s create another folder as v1. Now our folder structure looks something like this

app/controllers/api/v1

So all the API we create will be inside this folder.

Creating User model

Let’s say our user consists of attributes like fullname, email, gender and password. Go to the terminal and type this to create a user model.

rails g model User fullname:string email:string password_digest:string gender:integer

This will create all the necessary files such as the migration file, user model file, and test cases files.

Let’s do rails db:migrate in the terminal.

I also like to install a gem called annotate which is quite useful for reading the Active Record schema.

In the Gemfile add gem annotate inside the group development and test and then bundle install in the terminal. Now in the terminal, we can do

annotate --models --exclude tests,factories

If we go to the user.rb model we can see the schema information on the top.

Creating Users Controller

Before creating any API’s controller let’s create a controller a base_controller.rb first inside the api/v1 folder which will be inherited from the Application controller.

# frozen_string_literal: true

module Api
  module V1
    class BaseController < ApplicationController
    end
  end
end

Now again inside the folder api/v1 let’s create a User’s controller which will inherit from the Base controller.

So all the rules or code added in the base controller will apply to all our API’s controllers globally.

In the terminal enter this

rails generate controller api/v1/users index show create update delete

Our users_controller.rb will look like this

# frozen_string_literal: true

module Api
  module V1
    class UsersController < BaseController
      def index; end
      def show; end
      def create; end
      def update; end
      def delete; end
    end
  end
end

Using BCrypt for password encryption

If you have noticed above we have stored the password as a plain text in the databse which is not secure. We can use the bcrypt gem to secure the password.

Note: If we had used the devise gem then we didn’t have to worry about this, as devise itself can handle this.

The first thing we need to do is uncomment gem 'bcrypt', '~> 3.1.7' this guy in the gem file and then bundle install.

In the User model we then need to add has_secure_password and also active model validation for password presence.

class User < ApplicationRecord
  has_secure_password
  validates :email, :fullname, :password_digest, presence: true
  validates :email, uniqueness: true
end

Note: Your column name in users table must be password_digest and not password.

Now when ever a new user is created their password is saved as encrypted hash instead of plain text. Also one advantage of using bcrypt is that we can easily authenticate the user as well. Let’s check this in the rails c. For more information on how Bcrypt works you can read here

Setting up Routes

Let’s edit the generated route.rb file to something like this

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users
    end
  end
end

Now if we go to our terminal and hit rails route we can see our endpoints url for users.

Writing Test Cases for User model

Let’s write some test cases for the user model first also known as unit test. I like using the shoulda_matchers gem which provides the common test functionality.

In the Gemfile add gem 'shoulda-matchers', '~> 5.0'inside test group and hit bundle install, and in the rails_helpers.rb add this at the end.

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

And in the user_spec.rb let’s write some test cases

require 'rails_helper'

RSpec.describe User, type: :model do
  it { should validate_presence_of(:email) }
  it { should validate_presence_of(:fullname) }
  it {should validate_uniqueness_of(:email)}
  it {should validate_presence_of(:password)}
end

Let’s run the test cases and check if its pass or fails. In the terminal enter rspec spec/models Of course, this test will fail as we haven’t added any active record validation in the user model so lets add that

Our user.rb model will look something like this now

class User < ApplicationRecord
  validates :email, :fullname, :password_digest, presence: true
  validates :email, uniqueness: true
end

Let’s check the test again with rspec spec/models

Writing Test Cases for Users Controller

We have added the test for our user model likewise, let’s add the test for our user’s controller. If we go and see inside the spec folder we will already see a folder as requests/api/v1 which was auto-generated by rails when we did this

rails generate controller api/v1/users index show create update delete

We will be using this request spec for testing our user’s controller. As you can see that in the users_controller.rb we already have some empty methods which we can also call API and each of them has its unique endpoints.

Let’s first write some test cases for our def index method which is a GET method to retrieve all the existing users from the system.

What we should expect from the response of this method/api is an HTTP response status code of 200 and a list of users. So let’s add that in our test case which will look something like this now

require 'rails_helper'

RSpec.describe 'Api::V1::Users', type: :request do
  describe 'GET api/v1/users' do
    it 'returns list of available users' do
      create(:user)
      total_users = User.all.count
      get api_v1_users_path
      expect(response).to have_http_status(200)
      expect(JSON.parse(response.body).count).to eq(total_users)
    end
  end
end

Above the create(:user) is from the factory_bot_rails gem that we have installed.

If we run rspec spec/requests/api/v1/users_spec.rb in the terminal then we will see that the test will fail which is obvious cause we currently have an empty method. Let’s try to pass this test case by adding code in our def index GET method at users_controller.rb

def index
  render json: User.all, code: '200', status: :ok
end

Here we are sending all the available users in our system as a JSON response. Now let’s check the test case again if its passes or fails.
Noice, our test has passed and we have got our first api :tada: :tada:

Let’s write a test case for the API that creates a new user which will be a POST method i.e def create

This API is supposed to take the user information as request body params then according to it create a new user and finally give a response with an HTTP code status as 201. Also if we try to create a user with an existing email in the system then the API should respond with an error message.

So, considering this our test case will look something like

RSpec.describe 'Api::V1::Users', type: :request do
  describe 'GET api/v1/users/:id' do
    it 'returns a user' do
      user = create(:user)
      expected_response = { 'email' => user.email }
      get api_v1_user_path(id: user.id)
      expect(response).to have_http_status(200)
      expect(JSON.parse(response.body)).to include(expected_response)
    end
  end

  describe 'POST /api/v1/users/' do
    it 'creates a new user' do
      total_users = User.all.count
      request_body = {
        user: {
          fullname: 'New User',
          email: 'new@gmail.com',
          password: 'asdasd12ad'
        }
      }
      post api_v1_users_path, params: request_body
      expect(response).to have_http_status(201)
      expect(User.all.count).to eq(total_users + 1)
    end
  end
end

Our test case like before will obviously fail since we have not added anything to the def create method yet. In-order to pass this test case our method now will look something like this

def create
    user = User.new(user_params)
    if user.save
        render json: user, status: :created, code: '201'
    else
        render json: user.errors, status: :unprocessable_entity, code: '422'
    end
end

private

def user_params
    params.require(:user).permit(:fullname, :email, :password, :gender)
end

Above I have made use of super params and now if we run our test it will pass. We can add other examples in the test case as well like if the password is empty, fullname is empty, and so on.

Great 🎉 🎉 we have now another API and we can follow the similar process above to create all the required APIs.

Thanks for reading till the end we have come to the end of part 2 of the Building API with Rails series. In the upcoming parts, we will be adding active model serializers, SWAGGER for API documentation, and others.

The code base is available here. :beers:

Please feel free to give your feedback on the comment section below or ping me at or . Have a great time :smiley_cat:

Building API in Rails - Part 2