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 ...
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!