Dockerizing fullstack app with reactjs, expressjs, postgresql (Phần 1)

Chào mọi người, mình là Đại một lập trình viên fullstack và đây cũng là lần đầu tiên mình làm chuyện ấy ở viblo. Hi vọng những kiến thức mà mình chia sẽ qua những bài viết có thể phần nào giúp ích được cho các bạn 😀. Hôm nay mình sẽ chia sẽ "Như thế nào để kết hợp reactjs, expressjs, postresql và docker trong một ứng dụng ?". Nào hãy theo dõi bài viết này nhé 🙏

1. Chuẩn bị

Yêu cầu:

  • Đã có kiến thức cơ bản về reactjs, expressjs (lập trình nodejs), một chút về postgresql và docker.
  • Đã có kiến thức cơ bản về webpack, babel.
  • Môi trường mình sẽ demo:
    • window 10
    • node v8.11.3
    • yarn v1.7.0
    • Docker version 18.09.2, build 6247962

Mục đích:

  • Từng bước xây dựng một ứng dụng đơn giản với reactjs, expressjs, postgresql và docker.

Những phần bỏ qua:

  • Trong quá trình chúng ta thực hiện thì mình sẽ lượt bớt (không giải thích những thuật ngữ và các lệnh cơ bản) nhưng nếu không có cũng không sao, từ từ làm rồi cũng sẽ biết thôi nhé 😆
  • Bỏ qua các cấu hình cho môi trường production.
  • Cài đặt môi trường.
  • Bỏ qua css, validate, .etc

2. Tiến hành

Thư mục làm việc cơ bản như sau:

repo
  - client
  - server
  - database
  - docker-compose.yml

client

Đầu tiên chúng ta vào thư mục client và bắt đầu khởi tạo project cũng như tạo các file cần thiết.

Tạo file index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>App</title>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

Tạo file .babelrc để chỉ định các preset, plugin cho babel-loader

{
  "presets": [
    ["@babel/preset-env", { "modules": false }],
    ["@babel/preset-react", { "development": true }]
  ]
}

Tạo file webpack.config.js để bundle

const webpack = require('webpack')
const path = require('path')
const rootDir = path.resolve(process.cwd())
const srcPath = path.resolve(rootDir, 'src')

module.exports = {
  mode: 'development',
  entry: `${srcPath}/app.js`,
  output: {
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        include: srcPath,
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      API_URL: JSON.stringify(`http://localhost:9696/api`),
    }),
  ],
  devServer: {
    host: '0.0.0.0',
    port: 6969,
  },
  devtool: 'eval-source-map',
}

Cập nhật file package.json

{
  "name": "client",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "axios": "^0.19.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.6",
    "webpack": "^4.34.0",
    "webpack-cli": "^3.3.4",
    "webpack-dev-server": "^3.7.1"
  },
  "scripts": {
    "start": "webpack-dev-server --hot"
  }
}

Tạo file src/app.js. Ở đây mình chỉ làm một việc đơn giản là hiển thị danh sách sản phẩm thôi nhé 😀

import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'

const ProductList = ({ list }) => (
  <ul>
    {list.map(item => (
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
)

const App = () => {
  const [list, setList] = useState([])

  useEffect(() => {
    axios({ method: 'get', url: `${API_URL}/products` })
      .then(response => setList(response.data))
      .catch(error => console.error(error))
  }, [])

  return (
    <div>
      <h2>Product list</h2>
      <ProductList list={list} />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'))

Tạo file Dockerfile để lên kịch bản build image cho client

FROM node:10-alpine

RUN mkdir /usr/app
WORKDIR /usr/app
COPY package.json .
RUN yarn
COPY . .

CMD ["yarn", "start"]

Sau đó cập nhật file docker-compose.yml (Lưu ý khi sử dụng window thì phải setting Shared Drives để có thể sử dụng volumes nhé)

version: '3.3'
services:
  client:
    build:
      context: ./client
    container_name: app-client
    volumes:
      - ./client/src:/usr/app/src
    ports:
      - 6969:6969
    networks:
      - frontend_network
    environment:
      - CHOKIDAR_USEPOLLING=true
networks:
  frontend_network:

Cuối cùng là run nhẹ cái app client này bằng chạy lệnh docker-compose xem nó lên gì nhé 😎

docker-compose up client

Kết quả

Như vậy là đã tạo container thành công !

Thành công như mong đợi, các bạn đừng lo lắng về lỗi hiển thị ở console nhé vì mình chưa dựng serverdatabase lên nữa nên khi gọi api sẽ bị thế 👍

3. Kết luận

Tổng kết qua phần 1 này thì chúng ta đã dựng thành công container cho client, trong phần tiếp theo chúng ta sẽ tiếp tục dựng 2 phần còn lại là serverdatabase. Nhìn lướt qua thì phần source code mình viết ở trên cũng không có gì khó hiểu cho các bạn đã biết cơ bản. Cảm ơn bạn đã đọc bài viết của mình, hi vọng nó mang lai cho bạn thêm một chút kiến thức gì đó và chúng ta sẽ gặp nhau sớm trong phần kế tiếp 🙏 😜

Link repo tại đây https://github.com/daint2git/viblo.asia/tree/master/fullstack-reactjs-expressjs-postgresql-docker