داکرایز کردن یک اپلیکیشن React با استفاده از Node.js، PostgreSQL و Nginx


۰


این آموزش نشون میده چطور یک اپلیکیشن React رو با Node.js، Postgres و Nginx داخل Docker اجرا کنیم. قدم به قدم توضیح داده که چطوری بک‌اند، فرانت‌اند و سرور Nginx رو تنظیم کنیم و فایل‌های Dockerfile و docker-compose.yml رو آماده کنیم تا کل سیستم در کانتینرها اجرا بشه.

کد پروژه رو می‌تونید توی گیت‌هاب ببینید.


پیش‌نیازها

مطمئن بشید Docker و Node.js روی سیستم شما نصب شده باشن. من از Node ورژن 18.13.0 و Docker ورژن 20.10.24 استفاده کردم.


اپلیکیشن

نمای کلی اپلیکیشن


ساخت بک‌اند

یک اپ ساده می‌سازیم که به دیتابیس وصل میشه و چند endpoint برای فرانت‌اند تعریف می‌کنه.

شروع پروژه

داخل فولدر اصلی پروژه (مثلاً Project) یک فولدر جدید به نام node بسازید و یک پروژه Node.js با دستور زیر داخل فولدر node ایجاد کنید:

1npm init -y

این دستور یک فایل package.json می‌سازه که توش وابستگی‌های پروژه رو مشخص می‌کنیم.


وابستگی‌های بک‌اند

  • Express: فریم‌ورک وب برای Node.js برای مدیریت درخواست‌های کلاینت.
  • node-postgres: کلاینت Node.js برای اتصال به پایگاه داده Postgres.
  • Nodemon: ابزاری که به صورت خودکار سرور Node.js رو موقع تغییر فایل‌ها ری‌استارت می‌کنه.

دستور نصب:

1npm install express pg nodemon

در نهایت پوشه node_modules شامل تمام پکیج‌ها و وابستگی‌ها خواهد بود.


کدنویسی بک‌اند

داخل فولدر node یک فایل index.js بسازید و کدهای زیر را وارد کنید:

ابتدا ایمپورت‌ها:

1import pg from 'pg';
2import express from 'express';
3import bodyParser from 'body-parser';

راه‌اندازی اتصال به دیتابیس PostgreSQL با node-postgres:

1const { Client } = pg;
2
3const client = new Client({
4  user: 'postgres',
5  host: 'db',
6  database: 'postgres',
7  password: '1234',
8  port: 5432,
9});
10client.connect();

ایجاد جدول کاربران در دیتابیس:

1const createTable = async () => {
2  await client.query(`CREATE TABLE IF NOT EXISTS users
3  (id serial PRIMARY KEY, name VARCHAR (255) UNIQUE NOT NULL,
4  email VARCHAR (255) UNIQUE NOT NULL, age INT NOT NULL);`)
5};
6
7createTable();

راه‌اندازی Express و middleware برای پارس کردن درخواست‌های POST:

1const app = express();
2app.use(express.json());
3app.use(express.urlencoded({ extended: true }));

تعریف یک مسیر اولیه Hello World:

1app.get('/api', (req, res) => res.send('Hello World!'));

دریافت همه کاربران از جدول users:

1app.get('/api/all', async (req, res) => {
2  try {
3    const response = await client.query(`SELECT * FROM users`);
4    if(response){
5      res.status(200).send(response.rows);
6    }
7  } catch (error) {
8    res.status(500).send('Error');
9    console.log(error);
10  }
11});

درج کاربر جدید در جدول users:

1app.post('/api/form', async (req, res) => {
2  try {
3    const name  = req.body.name;
4    const email = req.body.email;
5    const age   = req.body.age;
6
7    const response = await client.query(`INSERT INTO users(name, email, age) VALUES ('${name}', '${email}', ${age});`);
8    if(response){
9      res.status(200).send(req.body);
10    }
11  } catch (error) {
12    res.status(500).send('Error');
13    console.log(error);
14  }
15});

راه‌اندازی سرور روی پورت 3000:

1app.listen(3000, () => console.log(`App running on port 3000.`));

تست مسیرها

در package.json اسکریپت شروع رو اضافه کنید:

1"start": "nodemon index.js"

و در انتها نوع ماژول رو بنویسید:

1"type": "module"

کل package.json شما باید چیزی شبیه این باشه:

1{
2  "name": "node",
3  "version": "1.0.0",
4  "description": "",
5  "main": "index.js",
6  "scripts": {
7    "test": "echo \"Error: no test specified\" && exit 1",
8    "start": "nodemon index.js"
9  },
10  "keywords": [],
11  "author": "",
12  "license": "ISC",
13  "dependencies": {
14    "express": "^4.18.2",
15    "nodemon": "^2.0.22",
16    "pg": "^8.11.0"
17  },
18  "type": "module"
19}

با اجرای npm start داخل فولدر node اپ شروع میشه ولی چون دیتابیس هنوز کانتینر نشده، با خطا مواجه میشید:

1Error: getaddrinfo EAI_AGAIN db
2...

راه حل: قبل از راه‌اندازی دیتابیس، این دو خط رو در index.js کامنت کنید:

1//client.connect();
2//createTable();

حالا npm start رو بزنید و در مرورگر به http://localhost:3000/api برید تا پیغام Hello World رو ببینید.

بعد از آزمایش فراموش نکنید که این خطوط رو دوباره از حالت کامنت خارج کنید!


ساخت فرانت‌اند

بریم سراغ فرانت‌اند. داخل فولدر پروژه دستور زیر رو اجرا کنید تا پروژه React با استفاده از Vite ساخته بشه:

1npm create vite react -- --template react

Vite ابزاری سبک و سریع برای ساخت پروژه‌های React هست مستندات Vite.

ساختار پروژه به شکل زیر هست:

ساختار پروژه React


وابستگی‌های فرانت‌اند

  • Axios: کلاینت HTTP برای ارسال درخواست‌ها.
  • React Router: کتابخانه مدیریت مسیرها در React.

نصب با دستور زیر داخل فولدر react:

1npm install axios react-router-dom

اجرای فرانت‌اند:

1npm run dev

در مرورگر به http://localhost:5173 مراجعه کنید صفحه شروع رو می‌بینید.


کدنویسی فرانت‌اند

داخل فایل App.jsx کد زیر را جایگزین کنید:

1import ReactDOM from "react-dom/client";
2import { BrowserRouter, Routes, Route } from "react-router-dom";
3import Layout from "./components/Layout";
4import Home from "./components/Home";
5import PostUser from "./components/PostUser";
6import GetAllUser from "./components/GetAllUser";
7
8export default function App() {
9  return (
10    <BrowserRouter>
11      <Routes>
12        <Route path="/" element={<Layout />}>
13          <Route index element={<Home />} />
14          <Route path="post" element={<PostUser />} />
15          <Route path="get" element={<GetAllUser />} />
16        </Route>
17      </Routes>
18    </BrowserRouter>
19  );
20}
21const root = ReactDOM.createRoot(document.getElementById('root'));
22root.render(<App />);

ایجاد کامپوننت‌ها

در فولدر src فولدر components بسازید و ۴ فایل زیر را ایجاد کنید:


GetAllUser.jsx

1import axios from "axios";
2import { useEffect, useState } from "react";
3
4const GetAllUser = () => {
5  const [users, setAllUser] = useState();
6  useEffect(() => {
7    axios
8      .get("http://localhost:8000/api/all")
9      .then((response) => setAllUser(response.data))
10      .catch((err) => {
11        console.error(err);
12      });
13  }, []);
14  return (
15    <>
16      <h1>All Users</h1>
17      <ul>
18      {users && users.map(user =>
19        <li key={user.id}>
20          <h3>ID: {user.id} </h3>
21          name: {user.name} <br/>
22          age: {user.age} <br/>
23          email: {user.email} <br/>
24        </li>
25      )}
26      </ul>
27    </>
28  );
29};
30
31export default GetAllUser;

Home.jsx

1const Home = () => {
2  return <h1>Home</h1>;
3};
4
5export default Home;

Layout.jsx

1import { Outlet, Link } from "react-router-dom";
2
3const Layout = () => {
4  return (
5    <>
6      <nav>
7        <ul>
8          <li><Link to="/">Home</Link></li>
9          <li><Link to="/post">Post User</Link></li>
10          <li><Link to="/get">Get All User</Link></li>
11        </ul>
12      </nav>
13      <Outlet />
14    </>
15  )
16};
17export default Layout;

PostUser.jsx

1import axios from "axios";
2import { useState } from "react";
3
4const PostUser = () => {
5  const [user, setUser] = useState({
6    name: '',
7    age: '',
8    email: '',
9  })
10  const createUser = async () => {
11    await axios
12      .post("http://localhost:8000/api/form",
13      user,
14      {
15        headers: {
16          'Content-Type': 'application/x-www-form-urlencoded'
17        }
18      })
19      .then((response) => {
20        setUser({
21          name: '',
22          age: '',
23          email: '',
24        })
25        console.log(response)
26        return alert("User Created: " + `${JSON.stringify(response.data, null,4)}`);
27      })
28      .catch((err) => {
29        return alert(err);
30      });
31  }
32  const onChangeForm = (e) => {
33    if (e.target.name === 'name') {
34      setUser({...user, name: e.target.value});
35    } else if (e.target.name === 'age') {
36      setUser({...user, age: e.target.value});
37    } else if (e.target.name === 'email') {
38      setUser({...user, email: e.target.value});
39    }
40  }
41  return (
42    <div>
43      <div>
44        <div>
45          <h1>Create User</h1>
46          <form>
47            <div>
48              <div>
49                <label>Name</label>
50                <input
51                  type="text"
52                  value={user.name}
53                  onChange={(e) => onChangeForm(e)}
54                  name="name"
55                  id="name"
56                  placeholder="Name"
57                />
58              </div>
59              <div>
60                <label>Age</label>
61                <input
62                  type="text"
63                  value={user.age}
64                  onChange={(e) => onChangeForm(e)}
65                  name="age"
66                  id="age"
67                  placeholder="Age"
68                />
69              </div>
70            </div>
71            <div>
72              <div>
73                <label htmlFor="exampleInputEmail1">Email</label>
74                <input
75                  type="text"
76                  value={user.email}
77                  onChange={(e) => onChangeForm(e)}
78                  name="email" id="email"
79                  placeholder="Email"
80                />
81              </div>
82            </div>
83            <button type="button" onClick={() => createUser()}>Create</button>
84          </form>
85        </div>
86      </div>
87    </div>
88  );
89};
90
91export default PostUser;

اصلاح css

داخل فایل index.css خط زیر را خط بزنید یا حذف کنید (برای بهتر شدن چیدمان):

1place-items: center;

حالا می‌تونید با اجرای npm run dev اپ را در http://localhost:5173 ببینید:

صفحه واکنش‌گرا


تغییر تنظیمات Vite برای Docker

داخل vite.config.js این تغییرات اعمال شود تا سرور روی همه آدرس‌ها باز باشه:

1import { defineConfig } from 'vite'
2import react from '@vitejs/plugin-react'
3
4// https://vitejs.dev/config/
5export default defineConfig({
6  plugins: [react()],
7  server: {
8    host: true,
9    //port: 5173, // در صورت استفاده جداگانه از docker این پورت تنظیم می‌شود
10  }
11})

پیکربندی سرور Nginx

Nginx به عنوان reverse proxy درخواست‌ها را دریافت کرده و به سرورهای مناسب (فرانت‌اند و بک‌اند) ارسال می‌کند.

در ریشه پروژه یک فولدر به نام nginx بسازید و یک فایل به نام default.conf با محتوای زیر داخلش قرار دهید:

1upstream front-end {
2    server front-end:5173;
3}
4
5upstream back-end {
6    server back-end:3000;
7}
8server {
9    listen 80;
10    location / {
11        proxy_pass http://front-end;
12    }
13    location /sockjs-node {
14        proxy_pass http://front-end;
15        proxy_http_version 1.1;
16        proxy_set_header Upgrade $http_upgrade;
17        proxy_set_header Connection "Upgrade";
18    }
19    location /api {
20        rewrite /back-end/(.*) /$1 break;
21        proxy_pass http://back-end;
22    }
23}

توضیحاتی درباره تنظیمات:

  • upstream گروهی از سرورها هستند که می‌تونیم بفرستیم درخواست رو.
  • در بلاک server، درخواست‌هایی که به / می‌رسن به فرانت‌اند فرستاده میشن.
  • درخواست‌های WebSocket به /sockjs-node مدیریت میشن.
  • درخواست‌های API با مسیر /api به بک‌اند فرستاده میشوند.

ساخت Dockerfile


Dockerfile فرانت‌اند

داخل فولدر react فایل Dockerfile ایجاد کنید و کد زیر را قرار دهید:

1FROM node:alpine
2
3WORKDIR /usr/src/app
4
5COPY . .
6
7RUN npm install
8
9EXPOSE 5173
  • FROM node:alpine: پایه از ایمیج سبک node استفاده می‌کنیم.
  • WORKDIR: پوشه کاری داخل کانتینر رو تنظیم می‌کنیم.
  • COPY: همه فایل‌های پروژه رو داخل کانتینر کپی می‌کنیم.
  • RUN npm install: نصب وابستگی‌ها.
  • EXPOSE: پورت 5173 که فرانت‌اند روش اجرا میشه.

Dockerfile بک‌اند

در فولدر node فایل Dockerfile بسازید با محتوای زیر:

1FROM node:alpine
2
3WORKDIR /usr/src/app
4
5COPY . .
6
7RUN npm install
8
9EXPOSE 3000

تنظیمات مشابه فرانت‌اند ولی پورت 3000 باز است.


Dockerfile برای Nginx

در فولدر nginx فایل Dockerfile بسازید:

1FROM nginx
2COPY ./default.conf /etc/nginx/conf.d/default.conf

ایمیج رسمی Nginx را می‌گیریم و کانفیگ بالا را جایگزین می‌کنیم.


پیکربندی docker-compose.yml

در فولدر ریشه پروژه فایل docker-compose.yml بسازید و کد زیر را قرار دهید:

1version: '3'
2
3services:
4
5  back-end:
6    build:
7      context: node
8    container_name: back-end
9    working_dir: /usr/src/app
10    networks:
11      - node-network
12    volumes:
13      - ./node:/usr/src/app
14      - /usr/src/app/node_modules
15    tty: true
16    ports:
17      - "3000:3000"
18    command: npm run start
19    depends_on:
20      - db
21
22  front-end:
23    build:
24      context: react
25    container_name: front-end
26    working_dir: /usr/src/app
27    networks:
28      - node-network
29    volumes:
30      - ./react:/usr/src/app
31      - /usr/src/app/node_modules
32    tty: true
33    ports:
34      - "5173:5173"
35    command: npm run dev
36
37  db:
38    image: postgres
39    container_name: db
40    restart: always
41    tty: true
42    volumes:
43      - ./data:/var/lib/postgresql/data
44    environment:
45      - POSTGRES_PASSWORD=1234
46    ports:
47      - "5432:5432"
48    networks:
49      - node-network
50
51  nginx:
52    build:
53      context: nginx
54    container_name: nginx
55    restart: always
56    tty: true
57    ports:
58      - "8000:80"
59    networks:
60      - node-network
61    depends_on:
62      - back-end
63      - front-end
64
65networks:
66  node-network:
67    driver: bridge

این فایل:

  • سرویس‌های بک‌اند، فرانت‌اند، دیتابیس و Nginx را مشخص می‌کند.
  • داده‌های Postgres داخل فولدر data خارج از کانتینر ذخیره می‌شود.
  • شبکه‌ای بنام node-network تعریف شده که کانتینرها به هم متصل هستند.

اجرای کل اپلیکیشن کانتینری

برای اجرای کل پروژه، در پوشه ریشه دستور زیر را بزنید:

1docker-compose up --build

بعد از راه‌اندازی می‌تونید در مرورگر http://localhost:8000 را باز کنید. Nginx شما را به اپ React هدایت می‌کند.


مثال

وقتی وارد گزینه "Post User" می‌شوید (آدرس http://localhost:8000/post)، می‌توانید کاربری جدید بسازید که در پایگاه داده Postgres ذخیره می‌شود.

دقت کنید که اگر مقدار age به صورت رشته وارد شود، خطا ظاهر خواهد شد چون توی جدول age نوع داده INT است و نباید خالی باشه.

بعد ثبت، اگر دکمه "Get all user" را بزنید (http://localhost:8000/get)، لیست کاربران، از جمله کاربر جدید رو می‌بینید:

لیست کاربران


درباره پوشه data

بعد از بالا آمدن کانتینرها، پوشه‌ای به نام data داخل ریشه پروژه ایجاد می‌شود که دیتای Postgres اونجا ذخیره میشه. این موضوع باعث میشه پس از حذف کانتینرها، اطلاعات دیتابیس شما حفظ بشه.

پوشه data


تبریک! شما با موفقیت یک اپلیکیشن React + Node.js + Postgres رو با Nginx و Docker کانتینری کردید و می‌تونید عملیات ثبت و دریافت کاربر رو انجام بدید.
هر سوالی داشتید در خدمتتون هستم!

انجین ایکس
داکر
جاوا اسکریپت

۰


نظرات


author
نویسنده مقاله: حمید فیض‌آبادی

کد با می متعهد است که بالاترین سطح کیفی آموزش را در اختیار شما بگذارد. هدف به اشتراک گذاشتن دانش فناوری اطلاعات و توسعه نرم افزار در بالاترین سطح ممکن برای درستیابی به جامعه ای توانمند و قدرتمند است. ما باور داریم هر کسی میتواند با استمرار در یادگیری برنامه نویسی چالش های خود و جهان پیرامون خود را بر طرف کند و به موفقیت های چشم گیر برسد. با ما در این مسیر همراه باشید. کد با می اجتماع حرفه ای برنامه نویسان ایرانی.

تمام حقوق این سایت متعلق به وبسایتcodebymeمیباشد.