Viblo CTF
+2

Xây dựng API với Grape

Hệ thống bạn đang xây dựng cần viết API, bạn đang băn khoăn không biết xây dựng API như thế nào cho dễ quản lí, mang lại hiệu quả cao. Bài viết này mình xin giới thiệu Grape, 1 gem hữu hiệu để quản lí API với nhiều ưu điểm nổi bật

Grape là gì?

Theo Grape thì grape là

Grape is a REST-like API framework for Ruby. It's designed to run on Rack or complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily develop RESTful APIs. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more. Grape là 1 REST-like API cho Ruby,. Nó được thiết kế để chạy trên Rack hoặc bổ sung cho mô hình ứng dụng web hiện có như Rails và Sinatra bằng việc cung cấp 1 DSL đơn giản để dễ dàng phát triển các RESTful API

Ưu điểm của Grape:

  • Khả năng phát triển nhanh, có thể sử dụng cùng Ruby on Rails hoặc sử dụng độc lập
  • Linh hoạt trong quản lí version
  • Có tốc độ sử lý cao hơn so với Rails-api

Cài đặt

Để cài đặt ta thêm cài đặt bằng cách

gem install grape

Hoặc thêm vào Gemfile:

gem 'grape'

Rồi sau đó bundle install Như vậy là đã cài gem grape thành công. Giờ chúng ta xây dựng cấu trúc file API. Ở trong controller ta tạo 1 folder api chuyên quản lí api của hệ thống

app
 |––controllers
       |––api

Xây dựng API

Grape API là ứng dụng Rack được tạo bằng các subclass kế thừa từ Grape::API

Ta tạo 1 file base trong api

app
 |––controllers
       |––api
           |––base.rb

Sau đó ta xây dựng base: dùng để khai báo mount trong đó. Mount có nghĩa các chức năng, code đó đã được tồn tại trong ứng dụng Rails

module API  
  class Base < Grape::API
    mount API::V1::Base
  end
end  

mount :

mount là từ khóa nói vs Rails app có 1 ứng dụng khác (thường là ứng dụng Rack và chính là Grape API) tồn tại.

Versioning

Grape framework cung cấp version. Nghĩa là chúng ta sẽ tổ chức API bên trong 1 phiên bản hoặc module v1. Các phiên bản trong tương lai chúng ta có thể phát triển khi ứng dụng phát triển sẽ được nested bên trong version 2, 3...

Tạo các module

Tao folder con v1 bên trong api

app
 |––controllers
       |––api
           |––base.rb
           |––v1

Với mỗi version ta cũng xây dựng 1 base riêng cho nó:

app
 |––controllers
       |––api
           |––base.rb
           |––v1
               |––base.rb

base này sẽ có trách nhiệm khai báo mount của các class khác

module API
  module V1
    class Base < Grape::API
      mount V1::Users
      # mount API::V1::AnotherResource
    end
  end
end

Chúng ta vừa mount cho class User. Giờ chúng ta hãy xây dựng nó Tạo users.rb trong v1:

app
 |––controllers
       |––api
           |––base.rb
           |––v1
               |––base.rb
               |––users.rb

Định nghĩa Endpoints

Giả sử ta muốn xây dựng 1 api method: GET để lấy toàn bộ danh sách User Trong app/controllers/api/v1/users.rb:

module API  
  module V1
    class Users < Grape::API
      include API::V1::Defaults

      resource :users do
        desc "Return all users"
        get "", root: :users do
          User.all
        end

        desc "Return a user"
        params do
          requires :id, type: String, desc: "ID of the user"
        end
        get ":id", root: "user" do
          User.where(id: params[:user_id]).first
        end
      end
    end
  end
end  

class Users bên trong function API giống như 1 controller Đầu tiên chúng ta định nghĩa Routes : resource :users. Sau đó lựa chọn phương thức (GET, POST..) sao cho phù hợp. Ở đây mình lấy danh sách toàn bộ users trong hệ thống và lấy thông tin của 1 user bất kì nên mình dùng method GET Sau đó ta dùng Postman theo địa chỉ http://localhost:3000/api/v1/users sẽ ra danh sách toàn bộ users.

Before và After:

beforeafter callbacks được gọi theo thứ tự sau:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Ví dụ:

  module API
  module V1
    class Users < Grape::API
      include API::V1::Defaults

      before do
        authenticate_user!
      end
     # .....
   end
 end

Helper

Có nhiều hàm sẽ dùng chung cho các api nên ta sẽ gom chúng vào 1 file gọi là defaults.rb trong app/controllers/api/v1/defaults.rb:

module API  
  module V1
    module Defaults
      extend ActiveSupport::Concern

      included do
        prefix "api"
        version "v1", using: :path
        default_format :json
        format :json
        formatter :json, 
             Grape::Formatter::ActiveModelSerializers

        helpers do
          def permitted_params
            @permitted_params ||= declared(params, 
               include_missing: false)
          end

          def logger
            Rails.logger
          end
        end
        
        # check authentice_user
        def authenticate_user!
            uid = request.headers["Uid"]
            token = request.headers["Access-Token"]
            @current_user = User.find_by(uid: uid)
            unless @current_user && @current_user.valid_token?(token)
              api_error!("You need to log in to use the app.", "failure", 401, {})
            end
          end
		
        # Hàm hiển thị errors message khi lỗi
        def api_error!(message, error_code, status, header)
           error!({message: message, code: error_code}, status, header)
        end
        
        #  # Hàm raise errors message khi lỗi
        def api_error_log(message)
            @logger ||= Logger.new(ProjectLogger.log_path("project_api"))
            @logger.info("=============#{Time.zone.now.to_s}==================\n")
            @logger.info("#{message}\n")
         end
          
        rescue_from ActiveRecord::RecordNotFound do |e|
          error_response(message: e.message, status: 404)
        end

        rescue_from ActiveRecord::RecordInvalid do |e|
          error_response(message: e.message, status: 422)
        end
        
        rescue_from Grape::Exceptions::ValidationErrors do |e|
          error_response(message: e.message, status: 400)
        end
        
      end
    end
  end
end  

Routes

Trong config/routes.rb ta thêm

Rails.application.routes.draw do  
  mount API::Base, at: "/"
end  

Rspec

Để viết rspec cho api ta có thể dùng gem airborne. Tham khảo tại Airborne Cài đặt:

gem install airborne

Ta thêm thư mục giống như sau:

spec
 |––api
       |––v1
           |––users_spec

Ví dụ 1 đoạn rspec cho 1 api cập nhập user

require "rails_helper"
require "airborne"

describe "API::V1::Users" do
  after(:all){I18n.locale = :ja}
  describe "POST api/v1/users" do
    let!(:user) do
      FactoryGirl.create :user, id: 1, email: "[email protected]", first_name: "James",
        last_name: "Bond", provider: "email"
    end

    context "when user update successfully" do
      let(:api_response){FactoryGirl.build(:api_update_user_success_response).deep_symbolize_keys}

      before do
        post("/api/v1/users", {first_name: "hitorri"},
          {"Accept-Language": "en", "App-Version": "knt/1.0", "Uid": user.uid, "Access-Token": user.access_token})
      end

      it{expect_json(api_response)}
    end
  end
 end

Grape API là 1 trong số công cụ đơn giản để viết API, nó mang lại hiệu quả cao nhờ cách thức viết đơn giản và hiệu suất cao. Sự kết hợp hoàn hảo giữa gem GrapeAirborne sẽ giúp bạn quản lí api một cách tốt nhất Hi vọng bài viết có thể giúp ích cho bạn khi xây dựng API!

Tham khảo: https://github.com/ruby-grape/grape https://github.com/ruby-grape/grape http://www.thegreatcodeadventure.com/making-a-rails-api-with-grap/


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.