Build API with Rails 5

Build API with Rails 5

The project here will be based on the project that I build myself. We will try to get the most of configuration that goes with API using Rails 5. Before we can build API with gem rails_api but since it is integrated into rails itself we can build API with flag --api. The advantage of using this flag is that rails removes unnecessary files such as views and assets since it is not needed to build API. And also many middlewares has been removed and leave only such as:

bin/rails middleware

use Rack::Cors
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Warden::Manager
run Rails::Application.routes

We won't go into these middleware but I just want you to know that many unnecessry middleware has been removed to make API run faster. ApplicationController now inherits from ActionController::API instead of ActionController::Base. As with middleware, this will leave out any Action Controller modules that provide functionalities primarily used by browser applications. To create a new rails app, run

rails new blog_api --api -T -d postgresql

we will be using postgresql as database by running -d postgresql and in this post we won't be writing any test so we run with -T. We will build a blog API which composts of posts and comments and users.

Configuration

We follow the standard HTTP Methods such as GET, POST, PUT, DELETE. We will make a namespace for this api by creating a namespace in routes by mapping it with controller by creating subfolder api under app/controllers.

Rails::Application.routes.draw do
  namespace :api do
  
  end
end

now the api endpoint looks something like this http://localhost/api we can also create something more readable for development by adding host name to /etc/hosts

127.0.0.1 api.blogapi.com

Now when we run rails s we can just input blogapi.com/api as url instead. Here as a good practice we also add subdomain and default Mime type json.

Rails::Application.routes.draw do
  namespace :api, defaults: {format: :json}, constraints: {subdomain: 'api'} do
  
  end
end

It looks abit ugly since endpoint now looks api.blogapi.com/api. we get rid of api at the end by adding path: '/' to the namespace. so now the routes looks as below:

Rails::Application.routes.draw do
  namespace :api, defaults: {format: :json}, constraints: {subdomain: 'api'}, path: '/' do
  
  end
end

Now the end point looks api.blogapi.com/ , much better, I think. We also add version for our api, so that we can have different version during the course of development by adding scope in the namespace. the routes look like:

Rails::Application.routes.draw do
  namespace :api, defaults: {format: :json}, constraints: {subdomain: 'api'} do
      scope 'v1', module: :v1 do
      
      end
  end
end

now our endpoint is api.blogapi.com/v1.

Building model

We will be using scaffold for fast demonstration. We will create three model User, Post and Comment.

rails g scaffold user name:string email:string password_digest:string token:string:unique
Running via Spring preloader in process 27951
      invoke  active_record
      create    db/migrate/20170325163856_create_users.rb
      create    app/models/user.rb
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
rails g scaffold Post title:string content:text user:references 
Running via Spring preloader in process 28232
      invoke  active_record
      create    db/migrate/20170325164319_create_posts.rb
      create    app/models/post.rb
      invoke  resource_route
       route    resources :posts
      invoke  scaffold_controller
      create    app/controllers/posts_controller.rb
rails g scaffold Comment content:text user:references post:references
Running via Spring preloader in process 28329
      invoke  active_record
      create    db/migrate/20170325164434_create_comments.rb
      create    app/models/comment.rb
      invoke  resource_route
       route    resources :comments
      invoke  scaffold_controller
      create    app/controllers/comments_controller.rb

Now we will edit add some constraints in model.

class User < ApplicationRecord
 has_many :posts
 has_many :comments
 has_secure_token
 has_secure_password
end
class Post < ApplicationRecord
  belongs_to :user
end
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :post
end

Now it is also time to change controller name under correct modules so we add Api::V1:: to all users_controllers.rb, posts_controller.rb and comments_controller.rb. and we all have to move these files into the right folders.

mv app/controllers/comments_controller.rb app/controllers/posts_controller.rb app/controllers/users_controller.rb app/controllers/api/v1

Now let create some data in db/seeds.rb to test whether it works properly.

Comment.delete_all
Post.delete_all
User.delete_all

5.times do |i|
  User.create(
    name: "user-#{i}",
    email: "email-#{i}",
    password_digest: "#{BCrypt::Password.create('password')}",
    token: "#{SecureRandom.base64}"
  )
end

User.all.each_with_index do |user, i|
  10.times do |n|
    user.posts.create(
      title: "title-#{i}_#{n}",
      content: "post-content-#{i}_#{n}",
    )
  end
end

Post.all.each_with_index do |post, i|
  User.all.each_with_index do |user, i|
    1.times do |n|
      Comment.create(
        content: "comment-content-#{i}_#{post.user.id}",
        user_id: user.id,
        post_id: post.id
      )
    end
  end
end

Now if you enter the url api.blogapi.com/v1/users, you will get all the users in database:

[{"id":71,"name":"user-0","email":"email-0","password_digest":"$2a$10$FroNrFAcoRsFG9grmy.UZO2YYUXODUotUh5.2H08zmW9jqTFrXhdK","created_at":"2017-03-25T20:36:04.386Z","updated_at":"2017-03-25T20:36:04.386Z","token":"5F8P4uvrnnjC/Iu2NkWjDg=="},{"id":72,"name":"user-1","email":"email-1","password_digest":"$2a$10$/WFbgBQgvnnwXlxTXY5WtuNT/KwXa1IlGzNHIDQNoB0ZI9dee7GAK","created_at":"2017-03-25T20:36:04.558Z","updated_at":"2017-03-25T20:36:04.558Z","token":"/iHfabZdfEmbYsbzMrGFUQ=="},{"id":73,"name":"user-2","email":"email-2","password_digest":"$2a$10$sE/foAOmicLrXkSGlXz8S.lpBAZoAQu68kfndhpdwA589hgWEz3/K","created_at":"2017-03-25T20:36:04.721Z","updated_at":"2017-03-25T20:36:04.721Z","token":"EjMzsaIzIikXh0M2Ls53CA=="},{"id":74,"name":"user-3","email":"email-3","password_digest":"$2a$10$Rgxa4jzQ6RCdtKVWo/53VuiLMk9/TCA3qb6UKn6zgyaOPfLU29lzm","created_at":"2017-03-25T20:36:04.887Z","updated_at":"2017-03-25T20:36:04.887Z","token":"A5942A9sENIhnaWmvoJXeg=="},{"id":75,"name":"user-4","email":"email-4","password_digest":"$2a$10$GzAuzBQE0PuMhvnN9AXxouSX2IrVbX55a06QXjhDEGvAPYmlR4qY.","created_at":"2017-03-25T20:36:05.054Z","updated_at":"2017-03-25T20:36:05.054Z","token":"7cXgWt20ZnA6WEWxB7jULA=="}]

Our Api works. OK, now the post is a bit long and there is still a few more step to go. So we will continue in the next post. Hope you enjoy it!