Kết hợp Rails API với ReactJs - Sử dụng "create-react-app"

Mở đầu

ReactJs (goi tắt là React) là một thư viện Javascript dùng để xây dựng UI, từ khi ra đời đến nay thì lúc nào cũng hót hòn họt, chắc các bạn cũng không còn ai xa lạ với với React nữa rồi nên mình sẽ bỏ qua phần giới thiệu nha. Ai mà chưa biết React thì tự tim hiểu đi nhá, ra đường bảo mình lập trình Web mà không biết React người ta cười cho chết 😃). Đùa vậy thôi, bạn nào chưa biết có thể tìm hiểu tại đây, dù sao thì những thứ "hot" như thế này thì mình cũng nên biết chứ nhể, cứ vọc thử đi biết đâu bạn lại yêu luôn ấy chứ 😉. Với những lập trình viên Rails thì chúng ta đã quen với việc sử dụng các thư viện bên ngoài qua các gem được viết sẵn, việc này sẽ giúp việc kết hợp giữa thư viện đó cùng với ứng dụng Rails dễ dàng hơn do việc cấu hình, thiết lập ban đầu được đã được thực hiện ở bên trong các gem. Việc kết hợp React với Rails cũng không phải là ngoại lệ, ta có thể sử dụng gem react-rails hoặc react-on-rails, cả hai đều giúp ta có thể dễ dàng tích hợp React vào asset pipeline của Rails. Mỗi gem đều có ưu nhược điểm riêng, bạn có thể tự tìm hiểu thêm để sử dụng chúng nhé 😃. Nhưng có một cách khác mà mình muốn đề cập kĩ hơn trong bài viết này, đó là chia ứng dụng ra làm hai phần, phần thứ nhất là ứng dụng Rails để cung cấp API, phần thứ hai là ứng dụng React để phục vụ cho việc hiển thị giao diện. Việc chia làm hai phần như vậy khiến từng phần có thể được phát triển một cách tách biệt (chỉ giao tiếp qua API), giúp dễ dàng hơn cho việc maintain ứng dụng khi nó ngày càng lớn. Ngoài ra, phần API có thể được dùng chung giữa ứng dụng Web và ứng dụng Android, iOS, rất tiện lợi khi bạn muốn phát triển ứng dụng trên nhiều nền tảng.

Giới thiệu create-react-app

create-react-app là một project được tạo ra bởi các lập trình viên của Facebook, với mục đích là bạn dễ dàng khởi tạo ứng dụng React cùng với một số thư viện đi kèm để bạn có thế sử dụng JSX, ES6 và các tính năng hữu ích khác như hot reloading, ... mà không phải tự tay cấu hình mọi thứ. Phần sau của bài viết, chúng ta sẽ cùng tìm hiểu cách để sử dụng create-react-app cùng với một Railss API server qua một ứng dụng nho nhỏ. Mình sẽ không đề cập đến những phần code React trong bài viết này. Vì thế nếu bạn không quen với React, hãy cứ tiếp tục theo dõi để có thể biết được các phần của một ứng dụng Web hiện đại liên kết với nhau như thế nào. Trong những phần tiếp theo, mình sẽ mặc định các bạn đã có những kiến thức cơ bản về Javascript/HTML/Rails rồi, còn nếu không thì hãy tự tìm hiểu thêm đi rồi quay lại đây cũng chưa muộn 😃.

Cùng bắt đầu nào

Bạn cần có Ruby, Rails, Bundler để quản lý ứng dụng Rails; Node.js, Npm để quản lý ứng dụng React. Ứng dụng mẫu của chúng ta là một ứng dụng đơn giản dùng để tra cứu khẩu phần dinh dưỡng của các món ăn. Dữ liệu được lấy từ cơ sở dữ liệu của Bộ Nông nghiệp Hoa Kỳ - USDA's National Nutrient Database Chúng ta sẽ xem qua một lần ứng dụng để có cái nhìn toàn cảnh. Sau đó mình sẽ hướng dẫn các bạn làm sao để tạo ra nó từ con số 0, bắt đầu từ việc tạo Rails server, sau đó là tạo ứng dụng React. Đầu tiên, clone repo đầy đủ code hoàn chỉnh về:

$ git clone [email protected]:fullstackreact/food-lookup-demo-rails

cd vào thư mục vừa down về. Có thể thấy các thư mục con của project như sau:

$ cd food-lookup-demo-rails
$ ls -1F
Gemfile
Gemfile.lock
Procfile
README.md
Rakefile
app/
bin/
client/
config/
config.ru
db/
flow-diagram.png
lib/
log/
public/
test/
tmp/
vendor/

Các bạn có thể thấy cấu trúc của một ứng dụng Rails ở trên. Nhưng có một thư mục hơi lạ là client, cùng xem kỹ hơn thư mục này xem sao:

$ ls -1F client
package.json
public/
src/

Ở đây file package.json sử dụng để quản lý các package được tạo ra bởi npm. Chắc các bạn cũng đã đoán ra, đây chính là thư mục chứa ứng dụng React của chúng ta. Từ đó ta các bạn có thể thấy được hai ứng dụng hoàn toàn riêng biệt đã được tạo ra trong repo này. Hãy cài đặt các dependencies cho cả hai project:

$ bundle && cd client && npm i && cd ..

Sau đó, tạo các bảng và seed dữ liệu:

$ bundle exec rake db:create db:migrate db:seed

Cuối cùng chúng ta sẽ chạy task khởi động server ở thư mục gốc qua lệnh:

$ rake start

Trình duyệt của bạn sẽ tự động mở locahost:3000 trong tab mới. Bạn sẽ thấy giao diện ứng dụng như trong hình:

Tạo API server

Trước tiên, tắt ứng dụng đang chạy với Ctrl + C. Sau đó, cùng xem ứng dụng sẽ như thế nào nếu chỉ có Rails server. Các bạn có 2 lựa chọn để có thể đến trạng thái này: Đơn giản sử dung Git branch hoặc tự tạo từ đầu.

Cách thứ nhất: Sử dụng nhánh starting-point

Nhánh starting-point chỉ chứa Rails server. Bạn có thể check out sang nhánh này bên trong repo:

$ git checkout starting-point

Các files bên trong thư mục client/ sẽ được xóa bỏ,nhưng bản thân thư mục client sẽ vẫn tồn tại do thư mục node_modules bên trong đang ở trang thái untracked. Ta sẽ xóa thư mục bằng cách thủ công:

$ rm -r client

Cách thứ hai: Tự làm hoi 😃

Để có thể bắt đầu, ta sẽ tạo mới Rails project:

$ rails new food-demo --api

--api là tùy chọn được thêm trong Rails 5, project được tạo với --api sẽ được loại bỏ những thành phần không cần thiết cho một Rails API-only server. Sau đó, việc cần làm là tạo food model, foods controller, cấu hình routes, migrate và seed cơ sở dữ liệu. Bạn nên tham khảo lại trong các files sau từ Github repo:

  • app/models/food.rb
  • app/controllers/foods_controller.rb
  • config/routes.rb
  • db/ (cả thư mục)

Kiểm tra API server

Trước khi muốn kiểm tra server có hoạt động không thì bạn phải bật server lên đã:

$ rails s

Server cung cấp một API qua /api/food, nhận 1 tham số duy nhất q - từ khóa để tìm kiếm món ăn. Bạn có thể sử dụng curl hoặc trình duyệt của mình để kiểm tra server. Ví dụ trong hình kết quả được đưa qua tool jq cho dễ nhìn:

$ curl "localhost:3000/api/food?q=restaurant,+italian" | jq '.'

[
  {
    "protein_g": 10.83,
    "carbohydrate_g": 11.36,
    "fat_g": 9.04,
    "kcal": 185,
    "description": "Restaurant, italian, lasagna w/ meat"
  },
  {
    "protein_g": 3.9,
    "carbohydrate_g": 17.77,
    "fat_g": 1.82,
    "kcal": 104,
    "description": "Restaurant, italian, spaghetti w/ pomodoro sau (no meat)"
  },
  {
    "protein_g": 7.07,
    "carbohydrate_g": 18.5,
    "fat_g": 4.85,
    "kcal": 154,
    "description": "Restaurant, italian, chs ravioli w/ marinara sau"
  },
  {
    "protein_g": 16.17,
    "carbohydrate_g": 10.92,
    "fat_g": 9.37,
    "kcal": 204,
    "description": "Restaurant, italian, chick parmesan wo/ pasta"
  },
  {
    "protein_g": 5.79,
    "carbohydrate_g": 16.4,
    "fat_g": 3.06,
    "kcal": 121,
    "description": "Restaurant, italian, spaghetti w/ meat sau"
  }
]

Có lẽ bạn đã chắc chắn là server có thể hoạt động bình thường và biết API hoạt động như thế nào rồi, giờ thì cùng xây dựng front-end cho trang web thôi. Kill server với Ctrl + C

Tạo ứng dụng React

Cài đặt create-react-app:

$ npm i -g create-react-app

Tiếp theo, ở thư mục gốc của project, chúng ta sẽ tạo ứng dụng React bên trong thư mục client, do đó chúng ta sẽ sử dụng create-react-app như sau:

$ create-react-app client

Thư mục mới client sẽ được tạo với cấu trúc:

$ ls -1F client
node_modules/
package.json
public/
src/
yarn.lock

Xem qua file client/package.json, chúng ta sẽ thấy các dependencies reactreact-dom

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^15.6.1",
    "react-dom": "^15.6.1"
  },
  "devDependencies": {
    "react-scripts": "1.0.7"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Vậy react-scripts trong devDependencies là cái gì?

react-scripts

react-scripts là một NPM package sử dụng cho create-react-app. Đó là một "hộp đen" chưa những thứ cần thiết:

  • Dependencies: Như Babel, ESLint và Webpack.
  • Configuration: Các file cấu hình cho Webpack, Babel và ESLint, cho cả 2 môi trường development và production .
  • Scripts: Ví dụ, chạy react-scripts start để thực hiện các lệnh thực thi cho package này, nó sẽ khởi động Webpack server trên môi trường development. Để có thể biết ứng dụng React đã được tạo thành công, hãy chạy npm start bên trong thư mục vừa tạo:
$ cd client && npm start

Webpack dev server sẽ được khởi động và tự động mở localhost:3000 trong tab mới trên trình duyệt của bạn: Hiện giờ chúng ta đang có API server ở thư mục gốc và có thể chạy để cung cấp API. Và chúng ta có một ứng dụng Node.js ở trong thư mục cient và chúng ta cũng có thể khởi động server cho nó. Vậy thì còn chờ đợi gì nữa, phần tiếp theo sẽ hướng dẫn bạn cách kết hợp chúng với nhau.

Kết hợp

Ta sẽ chạy server API ở localhost:3001 và ứng dụng React ở localhost:3000. Nhưng làm thế nào để 2 server này giao tiếp với nhau nhỉ? Ban đầu bạn có thể sẽ hình dung ra một luồng hoạt động như thế này: Trình duyệt sẽ tạo request đến localhost:3000, tải các static assets từ Webpack dev server. Từ đó trình duyệt sẽ tạo request trực tiếp đến API ở localhost:3001 với lệnh fetch dữ liệu:

fetch('localhost:3001/api/foods?q=carrots', {
  // ...
});

Tới đây có một vấn đề nảy sinh. Ứng dụng React của bạn (localhost:3000) muốn load các tài nguyên từ nguồn khác (localhost:3001). Để có thể làm điều này thì bạn cần thực hiện Cross-Origin Resource Sharing (CORS) do trình duyệt sẽ mặc định chặn các request kiểu này vì vấn đề bảo mật. create-react-app cung cấp một giải pháp để tương tác với API server trên môi trường development. Bạn có thể cho Webpack development server proxy request thay vì request trực tiếp lên API server, như sau: Trong luồng trên, React tạo request đến localhost:3000 - Webpack development server. Sau đó, development server proxy request đó đến API server. Từ đó, bạn cần: (1) Chạy cả Webpack development server và API server để ứng dụng có thể hoạt động. Và sau đó (2) làm thế nào đó để Webpack dev server proxy request đến API server Với phần (1), bạn có thể sử dụng terminal để chạy hai server ở hai cửa sổ khác nhau. Nhưng ta sẽ làm việc đó bằng cách "ngầu" hơn.

Foreman

Foreman là tiện ích để quản lý multiple process. Chúng ta thử xem nó hoạt động như thế nào nhé. Thêm foreman vào Gemfile:

gem 'foreman', '~> 0.82.0'

Cài đặt gem với bundler:

$ bundle install

Tạo file Procfile để chỉ định các process chạy bởi Foreman. Ta sẽ định nghĩa hai process: web cho ứng dụng React , api cho Rails server

web: cd client && npm start
api: rails s -p 3001

Chúng ta muốn ứng dụng React chạy trên cổng 3000, chạy lệnh sau:

$ foreman start -p 3000

Ta có thể thấy client app chạy trên localhost:3000 và API server đã bật, đang lắng nghe ở localhost:3001. Ctrl + C để kill cả hai tiến trình. Để đợn giản hơn, ta sẽ tạo một Rake task để thực thi lệnh trên. Tạo file lib/tasks/start.rake với nộ dung:

task :start do
  exec 'foreman start -p 3000'
end

Từ giờ ta có thể chạy ứng dụng Web của mình với:

$ rake start

React components

Như đã nói ở đầu bài viết, mình sẽ không đi vào phân tích những phần code React trong bài viết này. Vì thế bạn hãy lấy các file React components từ nhánh master sang, nếu bạn vẫn đang ở nhánh starting-point của project food-lookup-demo-rails.

git checkout master -- {client/src,client/semantic,client/semantic.json}

Nếu bạn tạo project bằng từ đầu rails new thì có thể copy file trên bằng cách thủ công. Chạy thử ứng dụng và bạn có thể sẽ gặp lỗi sau: Lỗi này là do create-react-app không cho bạn relative import ngoài các thư mục src/, packages/node_modules/, mình không rõ từ phiên bản nào thì có luật này nhưng nếu bạn mắc phải thì có thể thay đổi nội dung file index.js để import file semantic-ui có sẵn bên trong /src:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import './semantic-ui/semantic.min.css';

ReactDOM.render(
  <App />,
  document.getElementById('root'), // eslint-disable-line no-undef
);

Nếu chương trình không còn lỗi thì sẽ hiển thị như sau: Bạn có thể thấy hiện tại ứng dụng chưa có dữ liệu, mặc dù các component React đã được đưa vào đầy đủ. Đó là do chưa sử dụng proxy, chúng ta cần config Webpack một chút nữa.

Cài đặt proxy

Thêm dòng sau vào client/package.json:

// Bên trong client/package.json
"proxy": "http://localhost:3001/",

Vậy là xong. Ahihi. Chạy server rồi kiểm tra lại xem nào. Tất cả mọi thứ đã hoạt động. Oh yeah! 😃

Kết luận

Sau bài viết này các bạn đã biết cách sử dụng Rails API cùng với React bằng cách sử dung create-react-app và làm sao để hai project có thể kết nối với nhau thành ứng dụng Web hoàn chỉnh. Bài viết còn nhiều thiếu xót, mong các bạn đóng góp ý kiến để bản thân mình cùng bài viết hoàn thiện hơn. Cảm ơn các bạn đã theo dõi hết bài viết!

Nguồn tham khảo

https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/ Bạn nào muốn học React thì có thể vào trang trên và xem các bài viết khác nhé, hay lắm ❤️


All Rights Reserved