Node.js اور MySQL بیک اینڈ سسٹمز کی تعمیر کے لیے سب سے زیادہ قابل اعتماد اور جنگی آزمائشی امتزاج ہیں۔ Node.js ایک ایونٹ سے چلنے والا، غیر مسدود I/O ماڈل فراہم کرتا ہے جو ہم آہنگی کی درخواستوں کو مؤثر طریقے سے ہینڈل کرتا ہے، جبکہ MySQL متعلقہ ڈیٹا کی سالمیت فراہم کرتا ہے جس کی کاروباری ایپلی کیشنز کی ضرورت ہوتی ہے۔ مل کر، وہ ایک ایسی بنیاد بناتے ہیں جو روزانہ لاکھوں درخواستوں کو سنبھالنے والے اسٹارٹ اپ MVPs سے لے کر انٹرپرائز پلیٹ فارم تک ہر چیز کو طاقت دیتی ہے۔
یہ گائیڈ پروڈکشن گریڈ بیک اینڈ API کی تعمیر کے ذریعے چلتا ہے، جس میں پروجیکٹ ڈھانچہ، ڈیٹا بیس ڈیزائن، تصدیق، غلطی سے نمٹنے، اور تعیناتی کا احاطہ کیا گیا ہے۔
Express.js پروجیکٹ کا ڈھانچہ
کی ساخت کو اچھی طرح سے برقرار رکھنے کے قابل ہے۔ خدشات کو واضح طور پر الگ کریں اور جلد کنونشن قائم کریں۔project-root/
src/
config/
database.js
environment.js
logger.js
middleware/
auth.js
errorHandler.js
rateLimiter.js
validator.js
models/
User.js
Product.js
Order.js
index.js
routes/
auth.routes.js
users.routes.js
products.routes.js
orders.routes.js
index.js
services/
auth.service.js
user.service.js
product.service.js
email.service.js
utils/
ApiError.js
asyncHandler.js
pagination.js
app.js
server.js
migrations/
seeders/
tests/
.env
.env.example
package.jsonانٹری پوائنٹ ایکسپریس کو ضروری مڈل ویئر کے ساتھ ترتیب دیتا ہے:
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const { errorHandler } = require('./middleware/errorHandler');
const routes = require('./routes');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true,
}));
// Request parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Logging
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// API routes
app.use('/api/v1', routes);
// Error handling (must be last)
app.use(errorHandler);
module.exports = app;RESTful API ڈیزائن
اپنے API اختتامی پوائنٹس کو REST کنونشن کے بعد ڈیزائن کریں۔ وسائل کے لیے اسم استعمال کریں، اعمال کے لیے HTTP طریقے، اور مسلسل جوابی فارمیٹس۔
// src/routes/products.routes.js
const router = require('express').Router();
const { authenticate, authorize } = require('../middleware/auth');
const { validate } = require('../middleware/validator');
const { createProductSchema, updateProductSchema } = require('../validators/product');
const productController = require('../controllers/product.controller');
router.get('/', productController.getAll);
router.get('/:id', productController.getById);
router.post('/',
authenticate,
authorize('admin'),
validate(createProductSchema),
productController.create
);
router.put('/:id',
authenticate,
authorize('admin'),
validate(updateProductSchema),
productController.update
);
router.delete('/:id',
authenticate,
authorize('admin'),
productController.delete
);
module.exports = router;کنٹرولرز پتلے ہونے چاہئیں، جو سروس کلاسز کو بزنس لاجک ڈیلیگ کریں:
// src/controllers/product.controller.js
const productService = require('../services/product.service');
const { asyncHandler } = require('../utils/asyncHandler');
exports.getAll = asyncHandler(async (req, res) => {
const { page = 1, limit = 20, sort = 'created_at', order = 'DESC', search } = req.query;
const result = await productService.findAll({
page: parseInt(page),
limit: Math.min(parseInt(limit), 100),
sort,
order,
search,
});
res.json({
success: true,
data: result.products,
pagination: {
page: result.page,
limit: result.limit,
total: result.total,
totalPages: result.totalPages,
},
});
});
exports.create = asyncHandler(async (req, res) => {
const product = await productService.create(req.body);
res.status(201).json({
success: true,
data: product,
});
});MySQL کنکشن پولنگ کے ساتھ mysql2
کنکشن پولنگ کارکردگی کے لیے بہت اہم ہے۔mysql2پیکج ایک وعدے پر مبنی API فراہم کرتا ہے جس میں تیار بیانات اور کنکشن پولنگ باکس سے باہر ہے۔
// src/config/database.js
const mysql = require('mysql2/promise');
const logger = require('./logger');
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 3306,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: parseInt(process.env.DB_POOL_SIZE) || 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 30000,
timezone: '+00:00',
typeCast: function (field, next) {
if (field.type === 'TINY' && field.length === 1) {
return field.string() === '1';
}
return next();
},
});
// Test connection on startup
pool.getConnection()
.then(conn => {
logger.info('MySQL connected successfully');
conn.release();
})
.catch(err => {
logger.error('MySQL connection failed:', err.message);
process.exit(1);
});
module.exports = pool;SQL انجیکشن کو روکنے کے لیے ہمیشہ پیرامیٹرائزڈ استفسارات کا استعمال کریں:
// NEVER do this
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ALWAYS use parameterized queries
const [rows] = await pool.execute(
'SELECT id, email, first_name, last_name FROM users WHERE email = ?',
[email]
);Sequelize ORM
پیچیدہ ڈیٹا ریلیشنس والی ایپلی کیشنز کے لیے، Sequelize ایک مکمل خصوصیات فراہم کرتا ہے، جیسا کہ ORM کی تعریف کے ساتھ ماڈل اور Migration کی تعریف عمارت JWT
کے ساتھ
// src/models/Product.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/sequelize');
const Product = sequelize.define('Product', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING(255),
allowNull: false,
validate: {
notEmpty: true,
len: [2, 255],
},
},
description: {
type: DataTypes.TEXT,
allowNull: true,
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
validate: {
min: 0,
},
},
sku: {
type: DataTypes.STRING(100),
unique: true,
allowNull: false,
},
stock_quantity: {
type: DataTypes.INTEGER,
defaultValue: 0,
validate: {
min: 0,
},
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
}, {
tableName: 'products',
timestamps: true,
underscored: true,
paranoid: true, // Soft deletes
indexes: [
{ fields: ['sku'], unique: true },
{ fields: ['is_active'] },
{ fields: ['price'] },
{ fields: ['created_at'] },
],
});
// Associations
Product.associate = (models) => {
Product.belongsTo(models.Category, { foreignKey: 'category_id' });
Product.hasMany(models.OrderItem, { foreignKey: 'product_id' });
Product.belongsToMany(models.Tag, { through: 'product_tags' });
};
module.exports = Product;توثیق JSON ویب ٹوکنز کا استعمال کرتے ہوئے اسٹیٹ لیس تصدیق کو نافذ کریں۔ API درخواستوں کے لیے رسائی ٹوکنز کا استعمال کریں اور سیشن کے انتظام کے لیے ٹوکن ریفریش کریں۔// src/services/auth.service.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const ApiError = require('../utils/ApiError');
const SALT_ROUNDS = 12;
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
exports.register = async ({ email, password, firstName, lastName }) => {
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
throw new ApiError(409, 'Email already registered');
}
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const user = await User.create({
email,
password: hashedPassword,
first_name: firstName,
last_name: lastName,
});
const tokens = generateTokens(user);
return { user: sanitizeUser(user), ...tokens };
};
exports.login = async ({ email, password }) => {
const user = await User.findOne({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new ApiError(401, 'Invalid email or password');
}
const tokens = generateTokens(user);
return { user: sanitizeUser(user), ...tokens };
};
function generateTokens(user) {
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: ACCESS_TOKEN_EXPIRY }
);
const refreshToken = jwt.sign(
{ userId: user.id, tokenType: 'refresh' },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: REFRESH_TOKEN_EXPIRY }
);
return { accessToken, refreshToken };
}
function sanitizeUser(user) {
const { password, ...userData } = user.toJSON();
return userData;
}
// src/services/auth.service.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const ApiError = require('../utils/ApiError');
const SALT_ROUNDS = 12;
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
exports.register = async ({ email, password, firstName, lastName }) => {
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
throw new ApiError(409, 'Email already registered');
}
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const user = await User.create({
email,
password: hashedPassword,
first_name: firstName,
last_name: lastName,
});
const tokens = generateTokens(user);
return { user: sanitizeUser(user), ...tokens };
};
exports.login = async ({ email, password }) => {
const user = await User.findOne({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new ApiError(401, 'Invalid email or password');
}
const tokens = generateTokens(user);
return { user: sanitizeUser(user), ...tokens };
};
function generateTokens(user) {
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: ACCESS_TOKEN_EXPIRY }
);
const refreshToken = jwt.sign(
{ userId: user.id, tokenType: 'refresh' },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: REFRESH_TOKEN_EXPIRY }
);
return { accessToken, refreshToken };
}
function sanitizeUser(user) {
const { password, ...userData } = user.toJSON();
return userData;
}تصدیقی مڈل ویئر محفوظ راستوں پر ٹوکنز کی تصدیق کرتا ہے:
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const ApiError = require('../utils/ApiError');
exports.authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
throw new ApiError(401, 'Access token required');
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
throw new ApiError(401, 'Access token expired');
}
throw new ApiError(401, 'Invalid access token');
}
};
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
throw new ApiError(403, 'Insufficient permissions');
}
next();
};
};جوئی کے ساتھ ان پٹ کی توثیق
آنے والے تمام ڈیٹا کی تصدیق کریں اس سے پہلے کہ یہ آپ کے کاروباری منطق تک پہنچ جائے۔ Joi ایک طاقتور اسکیما پر مبنی توثیق لائبریری فراہم کرتا ہے۔
// src/validators/product.js
const Joi = require('joi');
exports.createProductSchema = Joi.object({
name: Joi.string().min(2).max(255).required(),
description: Joi.string().max(5000).optional(),
price: Joi.number().positive().precision(2).required(),
sku: Joi.string().alphanum().max(100).required(),
stock_quantity: Joi.number().integer().min(0).default(0),
category_id: Joi.string().uuid().required(),
tags: Joi.array().items(Joi.string().uuid()).optional(),
is_active: Joi.boolean().default(true),
});
exports.updateProductSchema = Joi.object({
name: Joi.string().min(2).max(255),
description: Joi.string().max(5000).allow(null),
price: Joi.number().positive().precision(2),
stock_quantity: Joi.number().integer().min(0),
category_id: Joi.string().uuid(),
is_active: Joi.boolean(),
}).min(1);
// src/middleware/validator.js
exports.validate = (schema) => {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, {
abortEarly: false,
stripUnknown: true,
});
if (error) {
const errors = error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message,
}));
return res.status(400).json({
success: false,
message: 'Validation failed',
errors,
});
}
req.body = value;
next();
};
};ایرر ہینڈلنگ مڈل ویئر
سنٹرلائزڈ ایرر ہینڈلنگ مستقل خرابی کے جوابات کو یقینی بناتی ہے اور حساس معلومات کو کلائنٹس تک پہنچنے سے روکتی ہے۔
// src/utils/ApiError.js
class ApiError extends Error {
constructor(statusCode, message, errors = []) {
super(message);
this.statusCode = statusCode;
this.errors = errors;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = ApiError;
// src/utils/asyncHandler.js
exports.asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// src/middleware/errorHandler.js
const logger = require('../config/logger');
exports.errorHandler = (err, req, res, next) => {
let statusCode = err.statusCode || 500;
let message = err.message || 'Internal Server Error';
// Sequelize validation errors
if (err.name === 'SequelizeValidationError') {
statusCode = 400;
message = 'Validation error';
}
// Sequelize unique constraint
if (err.name === 'SequelizeUniqueConstraintError') {
statusCode = 409;
message = 'Resource already exists';
}
// Log server errors
if (statusCode >= 500) {
logger.error({
message: err.message,
stack: err.stack,
url: req.originalUrl,
method: req.method,
ip: req.ip,
});
}
res.status(statusCode).json({
success: false,
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
...(err.errors?.length && { errors: err.errors }),
});
};استفسار کی اصلاح اور انڈیکسنگ
موثر ڈیٹا بیس کے سوالات بیک اینڈ کی کارکردگی کے لیے اہم ہیں۔ اپنے MySQL سوالات کو تیز رکھنے کے لیے ان حکمت عملیوں پر عمل کریں۔
- اکثر پوچھے گئے کالموں پر اشاریہ جات کا استعمال کریں- WHERE شقوں، JOIN حالات، اور ORDER BY بیانات میں استعمال ہونے والے کالموں پر اشاریہ جات شامل کریں۔ جامع اشاریہ جات کو سب سے بائیں سابقہ اصول پر عمل کرنا چاہیے۔
- منتخب کرنے سے گریز کریں *- ہمیشہ اپنے مطلوبہ کالموں کی وضاحت کریں۔ یہ ڈیٹا کی منتقلی کو کم کرتا ہے اور MySQL کو کورنگ انڈیکس استعمال کرنے کی اجازت دیتا ہے۔
- استفسارات کا تجزیہ کرنے کے لیے EXPLAIN کا استعمال کریں- عمل درآمد کے منصوبے کو سمجھنے کے لیے اپنے سوالات سے پہلے
EXPLAINچلائیں۔ مکمل ٹیبل اسکین، فائلسارٹ آپریشنز، اور عارضی میزیں تلاش کریں۔ - صفحہ بندی کو بہتر بنائیں- بڑے ڈیٹا سیٹس کے لیے، OFFSET کے بجائے کرسر پر مبنی صفحہ بندی (کی سیٹ صفحہ بندی) کا استعمال کریں، جو کہ اعلیٰ صفحہ نمبروں پر سست ہو جاتا ہے۔
// Inefficient OFFSET pagination
const [rows] = await pool.execute(
'SELECT * FROM products ORDER BY created_at DESC LIMIT ? OFFSET ?',
[limit, (page - 1) * limit]
);
// Efficient cursor-based pagination
const [rows] = await pool.execute(
`SELECT id, name, price, created_at FROM products
WHERE created_at < ?
ORDER BY created_at DESC
LIMIT ?`,
[cursor, limit]
);ڈیٹا بیس کی منتقلی
کبھی بھی پروڈکشن ڈیٹا بیس کو دستی طور پر تبدیل نہ کریں۔ ورژن کے زیر کنٹرول اسکیما تبدیلیوں کے لیے سیکوئلائز مائیگریشنز کا استعمال کریں۔
// migrations/20250101000000-create-products-table.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('products', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
},
name: {
type: Sequelize.STRING(255),
allowNull: false,
},
price: {
type: Sequelize.DECIMAL(10, 2),
allowNull: false,
},
sku: {
type: Sequelize.STRING(100),
unique: true,
allowNull: false,
},
category_id: {
type: Sequelize.UUID,
references: {
model: 'categories',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
created_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
},
});
await queryInterface.addIndex('products', ['sku']);
await queryInterface.addIndex('products', ['category_id']);
await queryInterface.addIndex('products', ['created_at']);
},
down: async (queryInterface) => {
await queryInterface.dropTable('products');
},
};شرح کی حد
اپنے API کو شرح کو محدود کرنے کے ساتھ غلط استعمال سے بچائیں۔ تقسیم شدہ تعیناتیوں کے لیے Redis اسٹور کے ساتھexpress-rate-limitاستعمال کریں۔
// src/middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('../config/redis');
exports.apiLimiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: {
success: false,
message: 'Too many requests, please try again later',
},
standardHeaders: true,
legacyHeaders: false,
});
exports.authLimiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 15 * 60 * 1000,
max: 5,
message: {
success: false,
message: 'Too many login attempts, please try again later',
},
skipSuccessfulRequests: true,
});ونسٹن کے ساتھ لاگنگ
پروڈکشن ایپلی کیشنز کو متعدد ٹرانسپورٹ اور لاگ لیولز کے ساتھ ساختی لاگنگ کی ضرورت ہے۔
// src/config/logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'api-server' },
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880,
maxFiles: 10,
}),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
}));
}
module.exports = logger;Docker تعیناتی
ماحول میں مسلسل تعیناتیوں کے لیے اپنی درخواست کو کنٹینرائز کریں۔
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder /app/node_modules ./node_modules
COPY src/ ./src/
COPY migrations/ ./migrations/
COPY package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "src/server.js"]# docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=mysql
- DB_USER=app_user
- DB_PASSWORD_FILE=/run/secrets/db_password
- DB_NAME=myapp
depends_on:
mysql:
condition: service_healthy
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: myapp
MYSQL_USER: app_user
MYSQL_PASSWORD_FILE: /run/secrets/db_password
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql_data:نتیجہ
ایک مضبوط Node.js اور MySQL پس منظر کی تعمیر کے لیے فن تعمیر، سلامتی، کارکردگی، اور آپریشنل خدشات پر توجہ دینے کی ضرورت ہے۔ پراجیکٹ کا صاف ستھرا ڈھانچہ قائم کرکے، درست تصدیق اور توثیق کو نافذ کرکے، ڈیٹا بیس کے سوالات کو بہتر بنا کر، اور اپنی تعیناتی کو کنٹینرائز کرکے، آپ ایک ایسا بیک اینڈ بناتے ہیں جو محفوظ، پرفارمنس اور برقرار رکھنے کے قابل ہو۔ اس گائیڈ میں بیان کردہ بنیادی اصولوں کے ساتھ شروع کریں، حقیقت پسندانہ بوجھ کے تحت اپنی ایپلیکیشن کی کارکردگی کی پیمائش کریں، اور آپ کو جو رکاوٹیں دریافت ہوئی ہیں ان پر اعادہ کریں۔ یہاں پیش کیے گئے پیٹرن ہزاروں پروڈکشن ایپلی کیشنز میں ثابت ہو چکے ہیں اور آپ کے بیک اینڈ سسٹمز کے لیے ایک مضبوط بنیاد کے طور پر کام کریں گے۔