ساختار بندی پروژه بکند
در این بخش مرحله به مرحله توسعه بکند پروژه را شرح میدهم.
- مراحل توسعه بکاند
- ساخت Repository
- ساختار بندی پروژه
- طراحی ساختار دیتابیس (ER-Diagram)
- نوشتن APi, Route , Controller
- نکات مهم پایانی
مراحل توسعه بکاند
در این قسمت بهصورت کامل مراحل توسعه بکاند با ساختار پروژه مورد نظر شرکت ماورا توضیح داده میشود.
فهرست مراحل
-
ایجاد Repository در گیتلب شرکت
- ورود بهhttps://git.mavaratech.comو لاگین با حساب کاربری شرکت
- کلیک روی New Project و انتخاب Create blank project
- وارد کردن نام، توضیحات و تنظیم سطح دسترسی -
تنظیم کانفیگ پروژه بر اساس ساختار تعریفشده
- ایجاد پوشهconfig/و فایلهایdefault.jsonوproduction.json
- ست کردن پارامترهایی مثلdatabase.url،baseUrlو تنظیمات SSO
- بارگذاری متغیرهای محیطی (Environment Variables) -
طراحی و پیادهسازی دیتابیس
- مدلسازی ER-Diagram و تعریف جداول، ستونها، PK و FK
- نوشتن اسکریپتهای DDL و اجرای آنها در محیط توسعه
- اضافه کردن seed data برای جداول پایه مثل کاربران و نقشها -
ایجاد Routeها، API و کنترلرها
- تعریف نقشه مسیرها (routes) درsrc/routes
- پیادهسازی کنترلرها (Controllers) برای هر endpoint درsrc/controllers
- مستندسازی APIها با OpenAPI/Swagger -
اطلاعرسانی به PMO درباره اتمام پروژه
- ارسال ایمیل یا تیکت در سیستم PMO مبنی بر پایان کار
- ارائه گزارش نهایی و لینک به مخزن کد و مستندات تکمیلی
ساخت Repository
مراحل گامبهگام «ساخت Repository» در گیتلب شرکت
-
ورود به گیتلب
ابتدا مرورگر خود را باز کنید و به آدرس زیر مراجعه کنید:
سپس با حساب کاربری شرکت (نام کاربری و رمز عبور) وارد شوید.https://git.mavaratech.com/ -
ایجاد پروژه جدید
- پس از ورود، از منوی سمت چپ یا بالای صفحه روی دکمهی New project کلیک کنید.
- گزینهی Create blank project را انتخاب کنید.
-
وارد کردن اطلاعات پروژه
- Project name: نام پروژه را به صورت انگلیسی و بدون فاصله (مثلاً
my-awesome-app) وارد کنید. - Project slug: معمولاً خودکار پر میشود اما در صورت نیاز ویرایش کنید.
- Project description (اختیاری): توضیح کوتاهی دربارهی هدف پروژه بنویسید.
- Visibility Level: سطح دسترسی پروژه را مشخص کنید (
Private،InternalیاPublic).
- Project name: نام پروژه را به صورت انگلیسی و بدون فاصله (مثلاً
-
ساخت ریپازیتوری
پس از تکمیل اطلاعات، روی دکمهی Create project کلیک کنید. گیتلب به طور خودکار مخزن خالی را برای شما ایجاد کرده و وارد صفحهی پروژه میشوید. -
پیکربندی اولیه (اختیاری)
- در صورت نیاز، یک فایل
README.md،.gitignoreیا لایسنس (LICENSE) اضافه کنید. - با فعال کردن Initialize repository with a README یک فایل README به مخزن اضافه میشود.
- در صورت نیاز، یک فایل
اکنون ریپازیتوری شما آمادهی کار است و میتوانید آن را کلون کرده، کدها را پوش کنید و توسعه را ادامه دهید.
ساختار بندی پروژه
مقدمه
در این راهنما، گامبهگام نحوهٔ شروع یک پروژه فولاستک با React و Node.js و ساختار پیشنهادی پوشهها و فایلها تشریح شده است. این مستند مناسب تیمهای توسعه است تا استاندارد یکپارچهای برای ایجاد پروژههای جدید داشته باشند.
راهاندازی پروژه بکاند (Node.js + Express)
mkdir backend
cd backend
npm init -y
npm install express dotenv sequelize jsonwebtoken bcryptjs
npm install --save-dev nodemon
ساختار پوشهها
backend/
├── config/
│ ├── db.js
│ ├── config.js
│ ├── development.js
│ ├── production.js
│ └── .dev.js
├── controller/
│ └── userController.js
├── routes/
│ └── userRoutes.js
├── service/
│ └── userService.js
├── utils/
│ └── generateToken.js
├── auth/
│ └── authMiddleware.js
├── .env
├── server.js
└── package.json
در server.js حتماً باید پورت پروژه را با مسئول DevOps تیم هماهنگ کنید.
پیکربندی اتصال به دیتابیس (config)
فایل db.js
این کلاس مسئول ایجاد اتصال Sequelize به PostgreSQL است:
import { Sequelize } from 'sequelize';
import configs from './config.js';
class DBConnection { constructor() { this.db_url = configs.db.url; const options = { dialect: "postgres", timezone: "+03:30", schema: 'schema', }; if (configs.db.useSSL === "true") { options.dialectOptions = { ssl: { require: true, rejectUnauthorized: false } }; } this.db = new Sequelize(this.db_url, options); }
getSequelizeInstance() { return this.db; } }
const sequelize = new DBConnection().getSequelizeInstance(); export default sequelize;
فایل config.js
براساس مقدار NODE_ENV یکی از فایلهای محیطی (development، production، .dev) وارد میشود:
const env = process.env.NODE_ENV || 'development';
const configs = await import(`./.${env}.js`).then(m => m.default);
export default configs;
پیکربندی اتصال به دیتابیس (config)
فایل db.js
این کلاس مسئول ایجاد اتصال Sequelize به PostgreSQL است:
import { Sequelize } from 'sequelize';
import configs from './config.js';
class DBConnection { constructor() { this.db_url = configs.db.url; const options = { dialect: "postgres", timezone: "+03:30", schema: 'schema Name', }; if (configs.db.useSSL === "true") { options.dialectOptions = { ssl: { require: true, rejectUnauthorized: false } }; } this.db = new Sequelize(this.db_url, options); }
getSequelizeInstance() { return this.db; } }
const sequelize = new DBConnection().getSequelizeInstance(); export default sequelize;
فایل config.js
براساس مقدار NODE_ENV یکی از فایلهای محیطی (development، production، .dev) وارد میشود:
const env = process.env.NODE_ENV || 'development';
const configs = await import(`./.${env}.js`).then(m => m.default);
export default configs;
فایلهای محیطی
در این فایلها تنظیمات مربوط به هر محیط (production، development، .dev) قرار دارد:
production.js
export default {
app: {
port: 3000,
apiBaseUrl: process.env.API_BASE_URL || 'url',
ssoBaseUrl: 'url',
paymentBaseUrl: process.env.PAYMENT_BASE_URL || 'url',
useMockUsername: process.env.USE_MOCK_USERNAME || 'false',
mockUsername: process.env.MOCK_USERNAME || '',
},
db: {
url: process.env.DB_URL || 'postgresql://postgreess',
useSSL: process.env.DB_USE_SSL || 'false',
},
redis: {
host: process.env.REDIS_HOST || 'host url',
port: process.env.REDIS_PORT || port,
sentinelNodes: process.env.REDIS_SENTINEL_NODES || '',
sentinelName: process.env.REDIS_SENTINEL_MASTER || 'mymaster',
password: process.env.REDIS_PASSWORD || 'password',
dbNo: process.env.REDIS_DB_NO || 0,
prefix: process.env.REDIS_PREFIX || 'integropia:'
}
};
development.js
export default {
app: {
port: 3001,
apiBaseUrl: 'BaseUrl that you recive',
useMockUsername: 'true',
mockUsername: process.env.MOCK_USERNAME || '',
},
db: {
url: process.env.DB_URL || 'postgresql:postgressName',
useSSL: 'false',
},
redis: {
host: process.env.REDIS_HOST || '87.248.137.22',
port: process.env.REDIS_PORT || 6379,
sentinelNodes: process.env.REDIS_SENTINEL_NODES || '',
sentinelName: process.env.REDIS_SENTINEL_MASTER || 'mymaster',
password: process.env.REDIS_PASSWORD || 'password',
dbNo: process.env.REDIS_DB_NO || 0,
prefix: process.env.REDIS_PREFIX || 'integropia:',
},
};
.dev.js
export default {
app: {
port: 3000,
apiBaseUrl: process.env.API_BASE_URL || 'url',
ssoBaseUrl: 'url,
paymentBaseUrl: process.env.PAYMENT_BASE_URL || 'url',
useMockUsername: process.env.USE_MOCK_USERNAME || 'false',
mockUsername: process.env.MOCK_USERNAME || '',
},
db: {
url: process.env.DB_URL || 'postgresql:postgress',
useSSL: process.env.DB_USE_SSL || 'false',
},
redis: {
host: process.env.REDIS_HOST || 'host',
port: process.env.REDIS_PORT || port,
sentinelNodes: process.env.REDIS_SENTINEL_NODES || '',
sentinelName: process.env.REDIS_SENTINEL_MASTER || 'mymaster',
password: process.env.REDIS_PASSWORD || 'password',
dbNo: process.env.REDIS_DB_NO || 0,
prefix: process.env.REDIS_PREFIX || 'integropia:'
}
};
نکته بسیار مهم: در این بخش باید secret_key مینیاپ فرانت خود را در فایلهای production.js یا development.js قرار دهید و نام مینیاپ در تمام پیکربندیها یکسان باشد.
طراحی ساختار دیتابیس (ER-Diagram)
در ادامه یک مثال از بخش طراحی دیتابیس آورده شده است
-
طراحی ساختار دیتابیس (ER-Diagram)
- مشخص کردن موجودیتها (Entities):User،RequestوRequestStatus
- تعریف روابط: هرUserمیتواند چندRequestداشته باشد؛ هرRequestچندRequestStatus
- تعیین کلیدهای اصلی (PK) و خارجی (FK) در هر جدول -
تنظیم کانفیگ Sequelize برای PostgreSQL
- نصب پکیجها:
- ایجاد فایل کانفیگnpm install sequelize pg pg-hstoresrc/configs/database.js:import { Sequelize } from 'sequelize'; const sequelize = new Sequelize( process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, { host: process.env.DB_HOST, dialect: 'postgres', logging: false, } );export default sequelize;
-
ایجاد مدل
User
- تعریف فیلدهای پایه مثلuserId،name،emailوrole
import { DataTypes } from 'sequelize'; import sequelize from '../configs/database.js';const User = sequelize.define('User', { userId: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, name: { type: DataTypes.STRING(100), allowNull: false, }, email: { type: DataTypes.STRING(150), allowNull: false, unique: true, }, role: { type: DataTypes.ENUM('user', 'expert', 'admin'), defaultValue: 'user', }, }, { tableName: 'users', timestamps: true, });
export default User;
-
ایجاد مدل
Request
- فیلدهایrequestId،userId،expertId،textوtrackingCode
import { DataTypes } from 'sequelize'; import sequelize from '../configs/database.js'; import User from './user.js'; import { v4 as uuidv4 } from 'uuid';const Request = sequelize.define('Request', { requestId: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, userId: { type: DataTypes.INTEGER, references: { model: User, key: 'userId' }, allowNull: true, }, expertId: { type: DataTypes.INTEGER, references: { model: User, key: 'userId' }, allowNull: true, }, text: { type: DataTypes.TEXT, allowNull: false, }, trackingCode: { type: DataTypes.STRING(20), allowNull: false, unique: true, defaultValue: () => uuidv4().slice(0, 20), }, }, { tableName: 'requests', timestamps: true, });
export default Request;
-
ایجاد مدل
RequestStatus
- ENUM وضعیتها، ارجاع بهrequestIdو نگهداریprevious_state
import { DataTypes } from 'sequelize'; import sequelize from '../configs/database.js'; import Request from './request.js';const RequestStatus = sequelize.define('RequestStatus', { statusId: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, requestId: { type: DataTypes.INTEGER, references: { model: Request, key: 'requestId' }, allowNull: false, }, statusType: { type: DataTypes.ENUM('started','pending','accepted','rejected','done'), defaultValue: 'started', }, expertComment: { type: DataTypes.TEXT, defaultValue: '', }, userComment: { type: DataTypes.TEXT, defaultValue: '', }, files: { type: DataTypes.JSONB, allowNull: true, }, previous_state: { type: DataTypes.INTEGER, references: { model: 'request_statuses', key: 'statusId' }, allowNull: true, }, }, { tableName: 'request_statuses', timestamps: true, hooks: { beforeCreate: async (status) => { const last = await RequestStatus.findOne({ where: { requestId: status.requestId }, order: [['statusId','DESC']], }); if (last) status.previous_state = last.statusId; } } }); export default RequestStatus;
-
تنظیم ارتباطات (Associations)
- در فایلsrc/models/index.jsیا انتهای هر مدل:
import User from './user.js'; import Request from './request.js'; import RequestStatus from './requestStatus.js'; // User ↔ Request User.hasMany(Request, { foreignKey: 'userId', as: 'Requests' }); Request.belongsTo(User, { foreignKey: 'userId', as: 'Customer' }); User.hasMany(Request, { foreignKey: 'expertId', as: 'ExpertRequests' }); Request.belongsTo(User, { foreignKey: 'expertId', as: 'Expert' }); // Request ↔ RequestStatus Request.hasMany(RequestStatus, { foreignKey: 'requestId', as: 'Statuses' }); RequestStatus.belongsTo(Request, { foreignKey: 'requestId', as: 'Request' }); export { User, Request, RequestStatus }; -
پیادهسازی الگوی وضعیت (State Pattern)
- ساخت پوشهsrc/models/state/و ایجاد کلاسهای وضعیت:
- در سرویس Request فراخوانی وضعیت:import { handleReqRejected } from '../../utils/status.utils.js'; class AcceptedState { constructor(context) { this.context = context; } async transitionTo(statusType) { if (!statusType) throw new Error('Invalid statusType'); if (statusType === 'rejected') { return await handleReqRejected(this.context); } throw new Error(`Cannot transition from accepted to ${statusType}`); } }export default AcceptedState;
import AcceptedState from './state/acceptedState.js'; import PendingState from './state/pendingState.js'; // … class RequestService { constructor(request) { this.request = request; this.state = this._getStateInstance(request.currentStatus); } _getStateInstance(type) { switch(type) { case 'accepted': return new AcceptedState(this.request); // … } } async changeStatus(toType) { return this.state.transitionTo(toType); } }
نوشتن APi, Route , Controller
در ادامه یک مثال از طراحی route , controller اورده شده است
-
ایجاد ساختار پوشهها برای Route و Controller
- در ریشه پروژه پوشههایsrc/routes/وsrc/controllers/را بسازید
- هر موجودیت (مثلاًrequestsوusers) یک فایل Route و یک فایل Controller دارد
- ساختار نهایی:src/ ├─ controllers/ │ ├─ requestController.js │ └─ userController.js └─ routes/ ├─ requestRoutes.js └─ userRoutes.js -
تعریف Routeها با استفاده از Express
- درsrc/routes/requestRoutes.jsمسیرهای CRUD را تعریف کنید:import { Router } from 'express'; import { createRequest, getAllRequests, getRequestById, updateRequest, deleteRequest } from '../controllers/requestController.js';const router = Router();
router.post('/', createRequest); router.get('/', getAllRequests); router.get('/:id', getRequestById); router.put('/:id', updateRequest); router.delete('/:id', deleteRequest);
export default router;
-
ایجاد Controllerها با منطق کاری
- درsrc/controllers/requestController.jsتوابع CRUD را پیاده کنید:import Request from '../models/request.js';export const createRequest = async (req, res, next) => { try { const newReq = await Request.create({ userId: req.body.userId, expertId: req.body.expertId, text: req.body.text }); res.status(201).json(newReq); } catch (err) { next(err); } };
export const getAllRequests = async (_req, res, next) => { try { const list = await Request.findAll(); res.json(list); } catch (err) { next(err); } };
export const getRequestById = async (req, res, next) => { try { const item = await Request.findByPk(req.params.id); if (!item) return res.status(404).json({ message: 'Not found' }); res.json(item); } catch (err) { next(err); } };
export const updateRequest = async (req, res, next) => { try { const [updated] = await Request.update(req.body, { where: { requestId: req.params.id } }); if (!updated) return res.status(404).json({ message: 'Not found' }); res.json({ message: 'Updated' }); } catch (err) { next(err); } };
export const deleteRequest = async (req, res, next) => { try { const deleted = await Request.destroy({ where: { requestId: req.params.id } }); if (!deleted) return res.status(404).json({ message: 'Not found' }); res.status(204).end(); } catch (err) { next(err); } };
-
وصل کردن Routeها به اپلیکیشن اصلی
- درsrc/app.jsیاindex.jsاین کار را انجام دهید:import express from 'express'; import bodyParser from 'body-parser'; import requestRoutes from './routes/requestRoutes.js'; import userRoutes from './routes/userRoutes.js';const app = express(); app.use(bodyParser.json());
app.use('/api/requests', requestRoutes); app.use('/api/users', userRoutes);
// هندلینگ خطا app.use((err, _req, res, _next) => { console.error(err); res.status(500).json({ error: err.message }); });
export default app;
نکات مهم پایانی
نکات مهم
- همیشه با تیم فرانتاند در تماس باشید تا پروژه در سریعترین زمان ممکن پیش برود.
- با مسئول DevOps هماهنگ کنید تا تنظیمات و استقرار بهدرستی انجام شود.
- زمانبندی را با تیم PMO چک کنید و مطابق بازهی اعلامشده کار را تحویل دهید.
- تستنویسی را فراموش نکنید تا کار برای تیم QA بهسادگی قابل بررسی باشد.
- مستندات پروژه را کامل و شفاف تهیه کنید تا فرآیند نگهداری و توسعه آینده آسان شود.