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.