Tạo API trong Rails 5
Như chúng ta đã biết thì Rails là Framework dùng để build 1 ứng dụng web, bên cạnh đó Rails còn hỗ trợ để xây dụng ứng dụng API. Nên trong loạt bài này mình sẽ giới thiệu đến các bạn việc xây dựng 1 ứng dụng API bằng rails như thế nào, về cơ bản sẽ giúp các bạn hình dung ra các tạo ứng dung API đơn ...
Như chúng ta đã biết thì Rails là Framework dùng để build 1 ứng dụng web, bên cạnh đó Rails còn hỗ trợ để xây dụng ứng dụng API. Nên trong loạt bài này mình sẽ giới thiệu đến các bạn việc xây dựng 1 ứng dụng API bằng rails như thế nào, về cơ bản sẽ giúp các bạn hình dung ra các tạo ứng dung API đơn giản với Rails.
Ứng dụng chúng ta sẽ xây dựng là Todo-list API.
Chúng ta sẽ xây dựng API dựa trên chuẩn RESTful API. và RESTful endpoints của ứng dụng sẽ là như sau :
Endpoint | Chức năng |
---|---|
POST /signup | Signup |
POST /auth/login | Login |
GET /auth/logout | Logout |
GET /todos | List all todos |
POST /todos | Create a new todo |
GET /todos/:id | Get a todo |
PUT /todos/:id | Update a todo |
DELETE /todos/:id | Delete a todo and its items |
GET /todos/:id/items | Get a todo item |
PUT /todos/:id/items | Update a todo item |
DELETE /todos/:id/items | Delete a todo item |
Chúng ta sẽ tạo 1 Project Todos api như sau:
$ rails new todos-api --api
- Chú ý 1 tý thì việc sử dụng "--api" ở đây chúng ta muốn một ứng dụng Rails API, tức là tạo ApplicationController kế thừa ActionController::API thay vì ActionController::Base trong các ứng dụng Rails chúng ta vẫn thường hay làm.
Chúng ta sẽ tạo Model Todo
$ rails g model Todo title:string created_by:string
Bằng việc sử dụng generate command chúng ta đã tạo 1 model Todo như sau:
class CreateTodos < ActiveRecord::Migration[5.0] def change create_table :todos do |t| t.string :title t.string :created_by t.timestamps end end end
Tiếp theo mà model Item
$ rails g model Item name:string done:boolean todo:references
Bằng việc sử dụng todo:references chúng ta đã tạo ra 1 liên kết giữa Item với Todo mode, và nó sẽ như thế này:
- Nó sẽ add 1 khóa ngoài todo_id trong bảng items
- Và từ đó chúng ta phải thiết lập liên kết belongs_to trong model Item
class CreateItems < ActiveRecord::Migration[5.0] def change create_table :items do |t| t.string :name t.boolean :done t.references :todo, foreign_key: true t.timestamps end end end
Giờ thì migrations để nó tạo bảng trong DB thôi:
$ rails db:migrate
Ở 2 model của chúng ta sẽ như thế này:
# app/models/todo.rb class Todo < ApplicationRecord # model association has_many :items, dependent: :destroy # validations validates_presence_of :title, :created_by end
# app/models/item.rb class Item < ApplicationRecord # model association belongs_to :todo # validation validates_presence_of :name end
Tiếp theo cái không thể thiếu đó mà Controller, chúng ta sẽ generate 2 controller Todos và Items
$ rails g controller Todos $ rails g controller Items
Giờ thì định nghĩa routes
# config/routes.rb Rails.application.routes.draw do resources :todos do resources :items end end
Trong routes chúng ta đang định nghĩa todo resources có một nested là items resources. điều này thể hiện cho quan hệ 1-many để có thể thấy được cụ thể các routes, ban có thể thực hiện :
$ rails routes
Giờ thì hãy định nghĩa Controller:
# app/controllers/todos_controller.rb class TodosController < ApplicationController before_action :set_todo, only: [:show, :update, :destroy] # GET /todos def index @todos = Todo.all json_response(@todos) end # POST /todos def create @todo = Todo.create!(todo_params) json_response(@todo, :created) end # GET /todos/:id def show json_response(@todo) end # PUT /todos/:id def update @todo.update(todo_params) head :no_content end # DELETE /todos/:id def destroy @todo.destroy head :no_content end private def todo_params # whitelist params params.permit(:title, :created_by) end def set_todo @todo = Todo.find(params[:id]) end end
json_response đây là hepler trả về JSON và HTTP status, cái này sẽ được định nghĩa trong concerns folder
# app/controllers/concerns/response.rb module Response def json_response(object, status = :ok) render json: object, status: status end end
- set_todo callback sẽ thực hiện tìm kiếm 1 todo bằng id, nhưng nếu trong trường hợp không có record nào tồn tại thì ActiveRecord sẽ ném ra 1 exception ActiveRecord::RecordNotFound, chúng ta sẽ xử lý exception này và trả về thông báo 404.
# app/controllers/concerns/exception_handler.rb module ExceptionHandler extend ActiveSupport::Concern included do rescue_from ActiveRecord::RecordNotFound do |e| json_response({ message: e.message }, :not_found) end rescue_from ActiveRecord::RecordInvalid do |e| json_response({ message: e.message }, :unprocessable_entity) end end end
create method trong TodosController, ở đây chúng ta sử dụng create! thay vì sử dụng create, cách này sẽ ném ra exception ActiveRecord::RecordInvalid vì thế chúng ta sẽ xử lý exception này trong ExceptionHandler module. Tuy nhiên nếu chỉ như vậy thì controller của chúng ta sẽ ko thể biết được về sự tồn tại của helpers này, vậy nên chúng ta sẽ including nó trong application controller
# app/controllers/application_controller.rb class ApplicationController < ActionController::API include Response include ExceptionHandler end
Vâng , đến đây thì có thể check xem nó chạy như thế nào được rồi .
$ rails s
và thực hiện tạo request với Postman hoặc có thể là httpie
# GET /todos $ http :3000/todos # POST /todos $ http POST :3000/todos title=123123 created_by=1 # PUT /todos/:id $ http PUT :3000/todos/1 title=qweqweqwe # DELETE /todos/:id $ http DELETE :3000/todos/1
Giờ thì chúng ta sẽ viết Controller co Item của Todo.
# app/controllers/items_controller.rb class ItemsController < ApplicationController before_action :set_todo before_action :set_todo_item, only: [:show, :update, :destroy] # GET /todos/:todo_id/items def index json_response(@todo.items) end # GET /todos/:todo_id/items/:id def show json_response(@item) end # POST /todos/:todo_id/items def create @todo.items.create!(item_params) json_response(@todo, :created) end # PUT /todos/:todo_id/items/:id def update @item.update(item_params) head :no_content end # DELETE /todos/:todo_id/items/:id def destroy @item.destroy head :no_content end private def item_params params.permit(:name, :done) end def set_todo @todo = Todo.find(params[:todo_id]) end def set_todo_item @item = @todo.items.find_by!(id: params[:id]) if @todo end end
và thực hiện kiếm tra API từ các request:
# GET /todos/:todo_id/items $ http :3000/todos/2/items # POST /todos/:todo_id/items $ http POST :3000/todos/2/items name='zxczxc' done=false # PUT /todos/:todo_id/items/:id $ http PUT :3000/todos/2/items/1 done=true # DELETE /todos/:todo_id/items/1 $ http DELETE :3000/todos/2/items/1
Trong phần sau chúng ta sẽ thực hiện Authentication với JWT (JSON WEB TOKEN).