วิธีการ Setup Server สำหรับ Node.js และ React ด้วย NGINX

วิธีการ Setup Server สำหรับ Node.js และ React ด้วย NGINX

990฿คอร์สสอนสร้างเว็บไซต์ HTML/CSS สำหรับมือใหม่ + พร้อม Workshop


สวัสดีครับ วันนี้จะพาไป Setup Server สำหรับ Deploy Backend และ Frontend คือ Node.js Application และ React App (ที่เป็น Single Page Application) ไว้ในเครื่องเดียวกัน โดยใช้ NGINX เป็น reverse proxy นะครับ

โดยปกติส่วนใหญ่แล้วส่วนใหญ่ SPA เป็น static website ดังนั้นมันสามารถ hosting ไว้ที่ไหนก็ได้ ไม่ว่าจะเป็น Netlify, Firebase Hosting, Github Pages แล้วใช้วิธี host Backend API ไว้อีกเครื่องนึง แต่สำหรับบางคนที่มีข้อจำกัด หรืออยาก Deploy ไว้ที่เครื่องเดียวกันเลยละ? จริงๆก็ทำไม่ยากเลย มาเริ่มกันดีกว่าครับ

Table of Contents

Node.js และ React App

หรือ clone จากนี้ครับ git clone https://github.com/Phonbopit/example-node-react.git

ก็เริ่มต้นมา สมมติผมมี Backend API เป็น Server ง่ายๆ ด้วย express 1 ไฟล์ครับ สร้างโปรเจ็คมาง่ายๆเลย

mkdir backend & cd backend
npm init -y
npm install express cors

ส่วนไฟล์ server.js ก็มีแค่ endpoint เดียวคือ /api เพื่อส่ง response กับไปที่ client

const express = require('express')
const cors = require('cors')
const app = express()

app.use(cors())

app.get('/api', (req, res) => {
  res.json({
    message: 'Ahoy!',
    users: [{
      id: 1,
      name: 'John Doe'
    }, {
      id: 2,
      name: 'Chuck Norris'
    }]
  })
})

app.listen(4000, () => console.log('It\'s work!'))

ต่อมา frontend ผมเลือกสร้างจาก Create React App

npx create-react-app frontend-app
npm install axios

จากนั้นผมแก้ไฟล์ App.js ให้ทำการ fetch data จาก /api

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import logo from './logo.svg';
import './App.css';

function App() {
  const [data, setData] = useState({})
  
  useEffect(() => {
    async function fetchData() {
      const response = await axios.get('http://localhost:3000/api')
      setData(response.data)
    }

    fetchData()
    return () => {}
  }, [])
	
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{data.message}</p>
        <ul>
          {data.users && data.users.map(user => {
            return <li key={user.id}>{user.name}</li>
          })}
        </ul>
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

ทดสอบรัน server ทั้ง backend และ frontend ครับ โดย

  • Backend อยู่ port 4000
  • Frontend อยู่ port 3000

ต่อมานับ App ขึ้น Server จะใช้ VPS หรืออะไรก็แล้วแต่ครับ สำหรับใครไม่มี Server หรือไม่รู้วิธี Setup อ่านบทความเกี่ยวกับ Digital Ocean เพิ่มเติมได้ที่ผมเขียนไว้ Digital Ocean คืออะไร ? + สอนวิธีการติดตั้งและสร้าง Droplet

รู้จักกับ PM2

สมมติตอนนี้เรามี App 2 ตัวแบ่งเป็น 2 folders คือ frontend และ backend สิ่งที่เราจะทำต่อมาคือ ติดตั้ง PM2 (ไม่เกี่ยวอะไรกับ PM 2.5 นะ)

PM2 ถ้าตามความหมายของมันเลยเขียนไว้ว่า PM2 is a Production Process Manager for Node.js applications เป็น Process Manager เอาไว้จัดการ Process ของ Node.js Application นั่นเอง สามารถสั่ง restart ดู stats ดู log มี Monitoring และ config อะไรได้อีกมากมาย

ซึ่งจริงๆเราก็รัน Node ได้ด้วยคำสั่งธรรมดาๆ เช่น node server.js นั่นแหละ เพียงแต่ว่าใช้ PM2 มันมีประโยชน์มากกว่าแค่การรัน Node

การใช้งาน PM2 เราต้องทำการติดตั้งแบบ global ซะก่อน ผ่าน NPM หรือ Yarn ก็แล้วแต่สะดวกเลยครับ

$ npm install [email protected] -g
# หรือ
$ yarn global add pm2

เมื่อติดตั้งเสร็จ เราสามารถใช้คำสั่ง เพื่อ start server ได้เลย

pm2 start backend/server.js

ซึ่งมันมีค่าเท่ากับการสั่งรัน node ปกติแหละ

node backend/server.js

เราสามารถดู List process ทั้งหมดที่รันด้วย PM2 ได้ด้วยคำสั่ง

pm2 l

┌────────┬────┬──────┬────────┬───┬─────┬───────────┐
│ Name   │ id │ mode │ status │ ↺ │ cpu │ memory    │
├────────┼────┼──────┼────────┼───┼─────┼───────────┤
│ server │ 0  │ fork │ online │ 00%  │ 21.3 MB   │
└────────┴────┴──────┴────────┴───┴─────┴───────────┘

จะสังเกตเห็นลักษณะแบบด้านบน คือจะมีระบุว่า Name เป็นอะไร และ id เป็นอะไร ตัวอย่างนี้ เราสามารถดูรายละเอียดได้ด้วยคำสั่ง

pm2 show <ID>
# เช่น
pm2 show 0

หรือจะดู Monitoring ก็ทำได้ด้วย

pm2 monit

และก็คำสั่งที่สำคัญของ PM2 อีกอย่างคือ การสั่งให้มัน start กรณีที่เครื่อง VPS ดับ หรือ restart (เพราะถ้าปกติเรารัน node server.js ถ้าเครื่องดับจบเลยใช่มั้ยละครับ)

pm2 startup

[PM2] Init System found: launchd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/local/Cellar/node/12.7.0/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup launchd -u YOUR_USER_NAME --hp /Users/YOUR_USER_NAME

และก็ copy ไอ้ตรงส่วน sudo env .... นั้นอะไปรัน มันก็จะรัน script daemon ให้เราเวลาเปิดเครื่องก็จะรัน Process (ขึ้นอยู่กับ OS อะไร Linux อาจจะเป็น systemctl)

สุดท้าย Save process list ไว้ครับ

pm2 save

สรุปคำสั่ง PM2 ที่เราต้องใช้ไว้รัน Server คือ (สามารถกำหนด name ให้มันได้ด้วย option --name)

# start server
pm2 start server.js --name="Awesome Name"

# monitoring
pm2 monit

# startup & save
pm2 startup
pm2 save

นอกเหนือจากนี้ PM2 ยังทำ deployment ได้ด้วยการกำหนด ecosystem.config.js เพื่อ deploy dev, staging, production อะไรพวกนี้ได้ครับ จากไฟล์ config นั่นเอง

รายละเอียดเพิ่มเติมของคำสั่ง PM2 สามารถอ่านเพิ่มเติมได้ที่นี่ PM2 Documentation

ทำ Reverse proxy ด้วย NGINX

ต่อมาเราจะทำ Reverse proxy ด้วย NGINX ก็ติดตั้ง NGINX เลยครับ (ผมสมมติว่ารันอยู่บน Ubuntu นะครับ)

sudo apt-get install nginx

จากนั้น ทำการเพิ่ม server block ของ nginx ตัวอย่างเช่น example.com เป็นโดเมนของเรา โดยเราจะ copy ตัวอย่างจาก default นั่นเอง

cd /etc/nginx/sites-available
cp default api.example.com

จากนั้นแก้ส่วน location / เป็นแบบนี้

server_name api.example.com;

location / {
    proxy_pass http://localhost:4000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
}

สามารถ เช็คว่า NGINX เรา setup ได้ถูกมั้ยด้วยคำสั่ง

sudo nginx -t

# จะได้ result ประมาณนี้
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

สุดท้าย Restart NGINX ซะ

sudo systemctl restart nginx

ตอนนี้ Backend ของเราถูก Deploy และรันด้วย PM2 จากนั้นใช้ NGINX เป็น reverse proxy จาก url ที่เราเข้า เช่น api.example.com จะแมพ port 4000 ให้เรา โดยที่ URL เราไม่ต้องใส่ api.example.com:4000 ครับ

ต่อมา Frontend เราจะ setup NGINX แบบนี้ครับ (Copy default เหมือนเดิม หรือจริงๆจะใช้ default ก็ได้ครับ)

cd /etc/nginx/sites-available
cp default example.com

จากนั้นแก้ไข เป็น path ที่อยู่ของไฟล์ dist ของเราครับ (หากใครยังไม่ได้ build production ก็รันก่อนนะครับ) ละก็อย่าลืมเปลี่ยน API endpoint จาก localhost เป็น URL ที่ตั้งค่าไว้ใน NGINX ด้วยนะครับ เช่น api.example.com

npm run build

ตัวอย่างเช่น path ที่อยู่หลังจาก build แล้วเป็น /var/www/frontend/build ที่ไฟล์ default หรือ example.com แก้ไขเป็น ตรง root เป็นแบบนี้

root /var/www/frontend/build;

Restart NGINX อีกรอบ เป็นอันเรียบร้อย

sudo systemctl restart nginx

ทีนี้เราก็จะได้ Frontend มี URL เป็น example.com และ Backend มี URL เป็น api.example.com โดยอยู่ที่เครื่องเดียวกันนั่นเอง

Happy Coding

Chai Phonbopit

Chai Phonbopit: Software Engineer แห่งหนึ่ง • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Node.js, JavaScript, React และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football นอกจากเขียนบล็อคที่เว็บนี้แล้ว ก็มีเขียนที่ https://medium.com/@Phonbopit ครับ