Use Rails as JSON-API Backend, part #1

part #1: CRUD

I'll write a few posts about how to use Rails just as a JSON-API Backend that can be consumed by different clients. No views, no assets, no sessions, just JSON.

There is already a gem called rails-api, that reduces Rails to a subset of the normal functionality to be more lightweight and so faster. I go without this gem here, we will just deactivate some functionality of Rails we don't need and so have no limitations due to another gem.


First, we deactivate components we don't need.

# config/application.rb

require "active_model/railtie"  
require "active_record/railtie"  
require "action_controller/railtie"  
require "action_mailer/railtie"  
# require "action_view/railtie"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"

We kick a lot out of the Gemfile, mainly Asset-Pipeline and view related things. In the beginning we just need Rails, a database adapter and a JSON serializer.

# Gemfile

source 'https://rubygems.org'

gem 'rails', '4.1.8'  
gem 'pg'  
gem 'active_model_serializers'  

Delete unneeded folders: app/assets, app/views, app/helpers


Disable sessions:

# config/initializers/session_store.rb
Rails.application.config.session_store :disabled  

Set protect_from_forgery to null_session

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base  
  protect_from_forgery with: :null_session
end  

`


OK, let's start with a thumb User model with two attributes:

rails g model User name age:integer  
rake db:migrate  

We define the v1 API endpoints for the user resource. Just the CRUD actions.

# config/routes.rb

Rails.application.routes.draw do  
  namespace :api do
    namespace :v1 do
      resources :users, except: [:new, :edit]
    end
  end
end  

Let's have a look at the CRUD controller. We just respond_to JSON and use the excellent respond_with method. I really like how Rails takes care about the response, its code and possible error messages, so that we have a clean, structured controller nearly without boilerplate code.

# app/controllers/api/v1/users_controller.rb

class Api::V1::UsersController < ApplicationController

  respond_to :json
  before_filter :find_user, only: [:show, :update, :destroy]

  def index
    @users = User.all
    respond_with @users
  end

  def show
    respond_with @user
  end

  def create
    @user = User.create(user_params)
    respond_with @user, location: url_for([:api, :v1, @user])
  end

  def update
    @user.update_attributes(user_params)
    respond_with @user
  end

  def destroy
    @user.destroy
    respond_with @user
  end


  private

  def find_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(
      :name,
      :age)
  end

end  

With a serializer we can control which attributes will be included in our JSON answer. We are not interested in created_at and updated_at, so we go with:

# app/serializers/user_serializer.rb

class UserSerializer < ActiveModel::Serializer  
  attributes :id, :name, :age
end  

`


That's the base! Fire up your Rails server. Let's play in the bash:

# create a user
curl  -H "Accept: application/json" -H "Content-type: application/json" http://127.0.0.1:3000/api/v1/users -X POST -d '{"user":{"name":"Dirk", "age": 33}}'  
--> Completed 201, {"user":{"id":1,"name":"Dirk","age":33}}

# retrieve the users collection
curl  -H "Accept: application/json" -H "Content-type: application/json" http://127.0.0.1:3000/api/v1/users  
--> Completed 200, {"users":[{"id":1,"name":"Dirk","age":33}]

# update the user
curl  -H "Accept: application/json" -H "Content-type: application/json" http://127.0.0.1:3000/api/v1/users/1 -X PATCH -d '{"user":{"age": 34}}'  
--> Completed 204

# read the user
curl  -H "Accept: application/json" -H "Content-type: application/json" http://127.0.0.1:3000/api/v1/users/1  
--> Completed 200, {"user":{"id":1,"name":"Dirk","age":34}}

# delete the user
curl  -H "Accept: application/json" -H "Content-type: application/json" http://127.0.0.1:3000/api/v1/users/1 -X DELETE  
--> Completed 204

In the next episodes I'll explain how to implement authentication and authorization and nevertheless keep the controller code clean.