Node.js और MySQL बैकएंड सिस्टम के निर्माण के लिए सबसे विश्वसनीय और युद्ध-परीक्षित संयोजनों में से एक हैं। Node.js एक इवेंट-संचालित, गैर-अवरुद्ध I/O मॉडल प्रदान करता है जो समवर्ती अनुरोधों को कुशलतापूर्वक संभालता है, जबकि MySQL व्यावसायिक अनुप्रयोगों की मांग के अनुसार संबंधपरक डेटा अखंडता प्रदान करता है। साथ में, वे एक ऐसी नींव बनाते हैं जो स्टार्टअप एमवीपी से लेकर एंटरप्राइज़ प्लेटफ़ॉर्म तक प्रतिदिन लाखों अनुरोधों को संभालने वाली हर चीज़ को शक्ति प्रदान करती है।
यह मार्गदर्शिका प्रोजेक्ट संरचना, डेटाबेस डिज़ाइन, प्रमाणीकरण, त्रुटि प्रबंधन और परिनियोजन को कवर करते हुए उत्पादन-ग्रेड बैकएंड 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;रेस्टफुल API डिज़ाइन
REST सम्मेलनों का पालन करते हुए अपने API एंडपॉइंट को डिज़ाइन करें। संसाधनों के लिए संज्ञाओं, कार्यों के लिए 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;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();
};
};जॉय के साथ इनपुट सत्यापन
आपके व्यावसायिक तर्क तक पहुंचने से पहले आने वाले सभी डेटा को सत्यापित करें। जॉय एक शक्तिशाली स्कीमा-आधारित सत्यापन लाइब्रेरी प्रदान करता है।
// 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 क्लॉज, जॉइन शर्तों और ऑर्डर बाय स्टेटमेंट्स में उपयोग किए गए कॉलम पर इंडेक्स जोड़ें। समग्र अनुक्रमणिका को सबसे बाएँ उपसर्ग नियम का पालन करना चाहिए।
- चयन से बचें *- हमेशा आवश्यक कॉलम निर्दिष्ट करें। यह डेटा स्थानांतरण को कम करता है और 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 बैकएंड के निर्माण के लिए वास्तुकला, सुरक्षा, प्रदर्शन और परिचालन संबंधी चिंताओं पर ध्यान देने की आवश्यकता है। एक स्वच्छ परियोजना संरचना स्थापित करके, उचित प्रमाणीकरण और सत्यापन लागू करके, डेटाबेस क्वेरीज़ को अनुकूलित करके, और अपनी तैनाती को कंटेनरीकृत करके, आप एक बैकएंड बनाते हैं जो सुरक्षित, निष्पादन योग्य और रखरखाव योग्य है। इस गाइड में बताए गए बुनियादी सिद्धांतों से शुरुआत करें, यथार्थवादी लोड के तहत अपने एप्लिकेशन के प्रदर्शन को मापें, और आपके द्वारा खोजी गई बाधाओं पर पुनरावृत्ति करें। यहां प्रस्तुत पैटर्न हजारों उत्पादन अनुप्रयोगों में सिद्ध हो चुके हैं और आपके बैकएंड सिस्टम के लिए एक ठोस आधार के रूप में काम करेंगे।