02/10/2018, 20:49

Rails 5 API cơ bản: Xây dựng 1 API Rails app

Trong bài viết này chúng ta sẽ cùng nhau build 1 app Rails API thuần, sử dụng Rails 5 và Ruby 2.5. Xin được gửi lời cảm ơn đến rails-api gem đã được tích hợp sẵn vào Rails 5 core, biến Rails đã và đang trở thành 1 ứng cử viên lý tưởng để xây dựng các API một cách nhanh chóng và dễ dàng. Cho đến ...

Trong bài viết này chúng ta sẽ cùng nhau build 1 app Rails API thuần, sử dụng Rails 5 và Ruby 2.5. Xin được gửi lời cảm ơn đến rails-api gem đã được tích hợp sẵn vào Rails 5 core, biến Rails đã và đang trở thành 1 ứng cử viên lý tưởng để xây dựng các API một cách nhanh chóng và dễ dàng.

Cho đến bây giờ, có lẽ lựa chọn tốt nhất để tạo APIs trong Ruby là Grape. Bên cạnh đó, vẫn có nhiều lợi thế khi sử dụng Rails 5 API, ví dụ như ActiveRecord mặc định, 1 cộng đồng developer mạnh mẽ, và có sẵn các tính năng asset pipeline lẫn giao diện người dùng - nếu như bạn cần để phát triển ứng dụng sau này.

Cài Rails 5

Đầu tiên, cần chắc chắn bạn đã cài Ruby bản 2.2.2 hoặc mới hơn và Rails 5

~ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]
~ rails -v
Rails 5.1.6

Theo như Rails guide, chúng ta cần tạo 1 app Rails thuần API bằng cách thêm tùy chọn --api vào command line khi khởi tạo 1 Rails app mới, như bên dưới:

rails new api_app --api

OK giờ ta đã có 1 API app mới hoàn toàn. Vào việc nào!

RSpec

Trước tiên cần setup RSpec để test trước. Để cài RSpec, chỉ cần thêm gem rspec-rails vào Gemfile trong group :development, :test.

group :development, :test do

  # Use RSpec for specs
  gem 'rspec-rails', '>= 3.5.0'

  # Use Factory Girl for generating random test data
  gem 'factory_girl_rails'
end

Chạy bundle và cài rspec:

bundle
rails g rspec:install 

OK!

Xây dựng API

Bắt đầu từ API controlers. Khi app được khởi tạo với option --api, ta có thể dùng generator scaffold mặc định để generate ra API resources như bình thường mà ko cần thêm đối số đặc biệt nào.

rails g scaffold user name email

Lệnh này sẽ generate ra cấu trúc file như bên dưới:

invoke  active_record
create    db/migrate/20181001081819_create_users.rb
create    app/models/user.rb
invoke    rspec
create      spec/models/user_spec.rb
invoke      factory_girl
create        spec/factories/users.rb
invoke  resource_route
route    resources :users
invoke  scaffold_controller
create    app/controllers/users_controller.rb
invoke    rspec
create      spec/controllers/users_controller_spec.rb
create      spec/routing/users_routing_spec.rb
invoke      rspec
create        spec/requests/users_spec.rb

Lưu ý rằng ko có file views nào được tạo ra khi chạy dưới API mode cả.

OK, giờ migrate lại data và chạy thử app nào ✌️

rails db:migrate
rails s

Xong phần setup, giờ sang phần quan trọng nào ✌️

Serializing API output

Chúng ta cần đổ dữ liệu dưới dạng chuổi JSON về các column trong database, vậy làm cách nào để quản lý điều này 1 cách tiện lợi nhất? Thông thường ta hay dùng các engine như jbuilder, tuy nhiên hiện tại app này ko dùng views nên ta sẽ ko chọn jbuilder. Thật may AMS(Active Model Serializers) đã cung cấp sẵn cho chúng ta 1 layer giữa model và controller, từ đây ta có thể gọi to_json hay as_json trên ActiveRecord object/collections như bình thường, trong khi vẫn xuất ra định dạng API như mong muốn.

Việc cần làm là thêm gem active_model_serializers và Gemfile:

gem "active_model_serializers"

Chạy bundle và tạo serializer mặc định cho model User:

bundle
rails g serializer user

Lênh này sẽ tạo ra file app/serializers/user_serializer.rb, ta sẽ thấy code như sau:

class UserSerializer < ActiveModel::Serializer
  attributes :id
end

Chú ý rằng chỉ có trường :id là được thêm vào mặc định, ta sẽ thêm trường :name và :email vào cùng:

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

Nếu model có các relationships khác, ta cần khai báo thêm vào serializers nếu muốn các thuộc tính này cũng đc serialize ở output

Ta cũng cần include ActionController::Serialization vào controller:

class ApplicationController < ActionController::API
  include ActionController::Serialization
end

OK! Giờ mỗi khi ta cần lấy data gì thì chỉ những thuốc tính đã khai báo trong UserSerializer mới được render ra. Tuyệt vời! Nếu bạn cần thêm các config khác, vui lòng xem hướng dẫn tại active_model_serializers.

Enabling CORS

Nếu muốn xây dựng 1 public API, ta cần quan tâm đến CORS (Cross-Orign Resource Sharing), đây là một cơ chế sử dụng các HTTP header bổ sung để báo cho trình duyệt cho phép ứng dụng web đặt tại server(domain ..) này có thể giao tiếp với 1 ứng dụng đặt tại server khác, hay còn gọi là giao tiếp chéo(cross-origin). App của ta sẽ tạo 1 cross-oringin HTTP request khi nó request đến một origin khác, hiểu sơ sơ vậy ✌️ .

Nghe thì có vẻ phức tạp, nhưng mà ta chỉ cần thêm rack-cors vào Gemfile:

gem "rack-cors"

Chạy bundle.

Ở file config/application.rb, thêm các config cần thiết vào, việc này sẽ cho phép GET, POST hay các OPTIONS requests từ bất cứ origin hay các nguồn khác:

module ApiApp
  class Application < Rails::Application
    ...

    config.middleware.insert_before 0, "Rack::Cors" do
      allow do
        origins '*'
        resource '*', :headers => :any, :methods => [:get, :post, :options]
      end
    end
  end
end

Với các options khác, bạn có thể tham khảo kĩ hơn ở đây .

Thêm phiên bản cho API (versioning)

Để tiện cho việc maintain hay phát triển thêm mà ko bị conflict gì một khi app đã chạy, ta cần cân nhắc việc thêm phiên bản cho app. Việc thêm phiên bản này sẽ chia nhỏ API của mình thành nhiều namespaces, như v1 và v2.

Ta có thể sử dụng cấu trúc như bên dưới để giữ controller code trông dễ nhìn hơn bằng cách dùng namespace Api::V1:

app/controllers/
.
|-- api
|   `-- v1
|       |-- api_controller.rb
|       `-- users_controller.rb
|-- application_controller.rb

Code controllers sẽ trông như thế này:

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

module Api::V1
  class ApiController < ApplicationController
    # Generic API stuff here
  end
end
# app/controllers/api/v1/users_controller.rb

module Api::V1
  class UsersController < ApiController
    # GET /v1/users
    def index
      render json: User.all
    end
  end
end

Tiếp theo, cần setup routes config/routes.rb :

scope module: 'api' do
  namespace :v1 do
      resources :users
  end
end

scope module: 'api' cho phép chúng ta định tuyến đến controller trong API module mà ko show nó lên url. Tuy nhiên, version v1/ là 1 phần trong url, nên ta đặt nó trong namespace.

Okie routes trông có vẻ ngon lành rồi.             </div>
            
            <div class=

0