Cách xây dụng một API đơn giản trong ứng dụng Rails của bạn <Part 1>

rsz_railsapi-1.jpg

Introduction

Một trong những lý do khiến cho người tiêu dùng phổ thông (không có hiểu biết về lập trình) tại Việt Nam không thực sự hiểu rõ về API là bởi tên gọi tiếng Việt khá tối nghĩa: giao diện lập trình ứng dụng. Lập trình ứng dụng thì đúng nghĩa, nhưng giao diện thì không hẳn là chính xác.

Nguyên gốc API viết đầy đủ trong tiếng Anh là Application Programming Interface, trong đó chữ Interface đang bị dịch thành giao diện. Thực ra, trong các bối cảnh khác thì cách dịch này là chính xác, ví dụ như GUI (Graphical User Interface) dịch thành giao diện đồ họa người dùng, còn CLI (Command Line Interface) dịch thành giao diện dòng lệnh. Từ giao diện ở đây được hiểu là bề mặt để con người tương tác với máy, như khi chúng ta dùng cửa sổ để tương tác với Windows hoặc dùng các câu lệnh để tương tác với DOS.

Nhưng nếu bạn mang cách hiểu giao diện như trong các cụm từ "giao diện cửa sổ", "giao diện cảm ứng", "giao diện iOS" để áp dụng vào từ giao diện trong API thì bạn đã hiểu sai. API là một giao diện giữa phần mềm với phần mềm.

API là cách để các phần mềm (hệ điều hành, ứng dụng, các module trong hệ thống doanh nghiệp...) giao tiếp với nhau và tận dụng năng lực của nhau.

Ví dụ một vài API lớn mà tôi và các bạn đã từng biết: Google API, Facebook API, Twitter API, PayPal API...

Bài viết hôm nay mình sẽ giới thiệu các bước cơ bản để xây dựng một API trong Rails là như thế nào, mời các bạn tìm hiểu các bưới bên dưới.

API Resource

Đầu tiên là chúng ta sẽ tách riêng phần API ra khỏi phần còn lại của ứng dụng. Để làm được điều đó chúng ta sẽ tạo một điều khiển (Controller) mới dưới một không gian tên riêng (namespace). Ở đây tôi sẽ đặt tên API sẽ là như sau (Các bạn có thể đặt khác cũng được tuy bạn): app/controllers/api/v1/, API của tôi sẽ nằm trong app/controller.

class Api::V1::BaseController < ApplicationController
end

Ở đây API của tôi nó được kế thừa từ ApplicationController hoặc bạn cũng có thể viết API kết thừa từ Controller nào đó, ví dụ: ActionController::Base. Chúng ta có thể ghi đè (override) các hàm bên trong nó.

Chúng ta cần phải vô hiệu hóa CSRF token và tắt cookie (no set-cookies header in response).

class Api::V1::BaseController < ApplicationController
  protect_from_forgery with: :null_session

  before_action :destroy_session

  def destroy_session
    request.session_options[:skip] = true
  end
end

Bây giờ chúng ta sẽ thêm router vào API bằng cách sử dụng giao thức RESTFul. Nhân tiện nói về giao thức RESTFul các bạn có thể tham khảo tài liệu mô tả về REST ở đây nhé, tài liệu viết rất đầy đủ và chi tiết (good).

api-2.jpg

Trả về dữ liệu khi gọi API chúng ta có thể trả về dữ liệu dưới dạng JSON hoặc cũng có thể gọi dữ liệu thông qua API REST để trả về dữ liệu dưới dạng siêu dữ liệu như (Hyperdata or Hypermedia).

#api
namespace :api do
  namespace :v1 do
    resources :users, only: [:index, :create, :show, :update, :destroy]
    resources :microposts, only: [:index, :create, :show, :update, :destroy]
  end
end

Khi trả về một bản ghi (record) chúng ta sử dụng phương thức GET trong giao thức REST. Dữ liệu trả về trong ActiveModelSerializers dưới dạng JSON serialization. Bây giờ tôi sẽ tạo 1 Controller trong API để lấy ra dữ liệu 1 bản ghi với phương thức GET trả về JSON như đoạn code bên dưới:

class Api::V1::UsersController < Api::V1::BaseController
  def show
    user = User.find(params[:id])

    render(json: Api::V1::UserSerializer.new(user).to_json)
  end
end

Một điều mà tôi muốn xây dựng các API trong Rails là bộ điều khiển là phải gắn gọn và dễ hiểu.

Tôi sẽ thêm 1 file user serializer để khởi tạo đối tượng người dùng: app/serializers/api/v1/user_serializer.rb

# app/serializers/api/v1/user_serializer.rb

class Api::V1::UserSerializer < Api::V1::BaseSerializer
  attributes :id, :email, :name,  :activated, :admin, :created_at, :updated_at

  has_many :microposts
  has_many :following
  has_many :followers

  def created_at
    object.created_at.in_time_zone.iso8601 if object.created_at
  end

  def updated_at
    object.updated_at.in_time_zone.iso8601 if object.created_at
  end
end

Nếu bây giờ tôi gửi 1 requestapi/v1/users/1 thì kết quả trả về dưới dạng json như sau:

{
  "user": {
    "id": 1,
    "email": "[email protected]",
    "name": "Vo Van Do",
    "activated": true,
    "admin": "admin",
    "created_at": "2016-09-22T13:21:28Z",
    "updated_at": "2016-09-22T13:21:28Z",
    "micropost_ids": [
      10,
      11
    ],
    "following_ids": [
      1,
      2
    ],
    "follower_ids": []
  }
}

Trường hợp request trả về nil, chúng ta sẽ điều hướng về lỗi 404 đã viết ở trên và kết quả trả về 404: Not found. Chúng ta sẽ phải xây dựng hàm trả về lỗi dữ liệu không tồn tại hay request trả về nil. Các bạn có thể xem đoạn code bên dưới:

rescue_from ActiveRecord::RecordNotFound, with: :not_found

def not_found
  return api_error(status: 404, errors: 'Not found')
end

Như vậy chúng ta đã xử lý xong trường hợp ngoại lệ mà request trả về khi không có dữ liệu.

Hàm Index

Bài toán đặt ra là làm sao để có thể lấy hết dữ liệu của một đối tượng nào đó. Thì trong hàm này chúng ta sẽ làm điều đó.

Trong hàm Index này chúng ta sẽ lấy ra tất cả bản ghi được lưu trữ của 1 đối tượng nào đó, cụ thể ở đây mình sẽ lấy ra tất cả dữ liệu của đối tượng User. Ở đây chúng ta vẫn sử dụng phương thức GET trong giao thức REST để trả về dữ liệu.

class Api::V1::UsersController < Api::V1::BaseController
  def index
    users = User.all

    render(
      json: ActiveModel::ArraySerializer.new(
        users,
        each_serializer: Api::V1::UserSerializer,
        root: 'users',
      )
    )
  end
end

Khá dễ dàng phải không?

Dữ liệu trả về khi gọi hàm index sẽ như sau:

{
  "user": {
    "id": 1,
    "email": "[email protected]",
    "name": "Vo Van Do",
    "activated": true,
    "admin": "admin",
    "created_at": "2016-09-22T13:21:28Z",
    "updated_at": "2016-09-22T13:21:28Z",
    "micropost_ids": [
      10,
      11
    ],
    "following_ids": [
      1,
      2
    ],
    "follower_ids": []
  },
  "user": {
    "id": 2,
    "email": "[email protected]",
    "name": "Test",
    "activated": true,
    "admin": "guest",
    "created_at": "2016-09-22T13:21:28Z",
    "updated_at": "2016-09-22T13:21:28Z",
    "micropost_ids": [
      7,
      8
    ],
    "following_ids": [
      4,
      5
    ],
    "follower_ids": [
      5
    ]
  },
  .......................
}

Đơn giản phải không các bạn? Ngoài ra bạn có thể sử dụng thêm Gem kollegorna để thêm các điều kiện params và trong hàm index. Ở ví dụ code bên dưới tôi sẽ include thêm gem kollegorna vào hàm index, code sẽ như bên dưới:

class Api::V1::UsersController < Api::V1::BaseController
  include ActiveHashRelation

  def index
    users = User.all

    users = apply_filters(users, params)

    render(
      json: ActiveModel::ArraySerializer.new(
        users,
        each_serializer: Api::V1::UserSerializer,
        root: 'users',
      )
    )
  end
end

So với code phần trên thì bạn đã thấy có thêm 2 sự thay đổi, đó là chúng ta thêm dòng include ActiveHashRelation và thêm dòng users = apply_filters(users, params).

Đến đây là kết thúc phần 1 của bài viết, các bạn nhớ theo dõi bài viết số 2 nhé, có rất nhiều điều thú vị ở bài số 2. Ok, chúc các bạn buổi tối vui vẻ.

Kết Luận

Bài viết này đã giới thiệu đến các bạn các bước đầu tiên cần chuẩn bị để xây dựng API trong Rails.

API ngày càng được sử dụng rất rộng dãi trong lập trình ứng dụng nói riêng hay trong lập trình nói chung. Nếu thiếu API thì tôi tin chắc rằng thế giới này sẽ không còn chuyển động theo đúng quỹ đạo của nó nữa.

Tài liệu tham khảo


All Rights Reserved