۰
این آموزش نشون میده چطور یک اپلیکیشن 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 میسازه که توش وابستگیهای پروژه رو مشخص میکنیم.
دستور نصب:
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:
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;
داخل فایل index.css خط زیر را خط بزنید یا حذف کنید (برای بهتر شدن چیدمان):
1place-items: center;
حالا میتونید با اجرای npm run dev
اپ را در http://localhost:5173 ببینید:
داخل 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 به عنوان 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
، درخواستهایی که به /
میرسن به فرانتاند فرستاده میشن./sockjs-node
مدیریت میشن./api
به بکاند فرستاده میشوند.داخل فولدر 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 که فرانتاند روش اجرا میشه.در فولدر node فایل Dockerfile بسازید با محتوای زیر:
1FROM node:alpine 2 3WORKDIR /usr/src/app 4 5COPY . . 6 7RUN npm install 8 9EXPOSE 3000
تنظیمات مشابه فرانتاند ولی پورت 3000 باز است.
در فولدر nginx فایل Dockerfile بسازید:
1FROM nginx 2COPY ./default.conf /etc/nginx/conf.d/default.conf
ایمیج رسمی Nginx را میگیریم و کانفیگ بالا را جایگزین میکنیم.
در فولدر ریشه پروژه فایل 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
این فایل:
برای اجرای کل پروژه، در پوشه ریشه دستور زیر را بزنید:
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 داخل ریشه پروژه ایجاد میشود که دیتای Postgres اونجا ذخیره میشه. این موضوع باعث میشه پس از حذف کانتینرها، اطلاعات دیتابیس شما حفظ بشه.
تبریک! شما با موفقیت یک اپلیکیشن React + Node.js + Postgres رو با Nginx و Docker کانتینری کردید و میتونید عملیات ثبت و دریافت کاربر رو انجام بدید.
هر سوالی داشتید در خدمتتون هستم!
۰
کد با می متعهد است که بالاترین سطح کیفی آموزش را در اختیار شما بگذارد. هدف به اشتراک گذاشتن دانش فناوری اطلاعات و توسعه نرم افزار در بالاترین سطح ممکن برای درستیابی به جامعه ای توانمند و قدرتمند است. ما باور داریم هر کسی میتواند با استمرار در یادگیری برنامه نویسی چالش های خود و جهان پیرامون خود را بر طرف کند و به موفقیت های چشم گیر برسد. با ما در این مسیر همراه باشید. کد با می اجتماع حرفه ای برنامه نویسان ایرانی.