API Response formatini standartlashtirish
Muammodan boshlaylik
Bir loyihada turli endpointlar turli formatda javob qaytaradi:
// Birinchi dasturchi — foydalanuvchilar
res.json({ users: [...], total: 50 });
// Ikkinchi dasturchi — mahsulotlar
res.json([...]); // ← to'g'ridan-to'g'RI massiv
// Uchinchi dasturchi — buyurtmalar
res.json({ data: {...}, status: 'ok' });
// Xato holatlari ham har xil:
res.status(404).json({ message: 'Topilmadi' });
res.status(400).json({ error: 'Noto\'g\'ri' });
res.status(422).json({ errors: [...] });
Frontend dasturchi har endpoint uchun alohida kod yozishi kerak:
// ❌ — har yerda turli tekshiruv
const res1 = await fetch('/api/users');
const data1 = await res1.json();
const users = data1.users; // ← bu yerda .users
const res2 = await fetch('/api/products');
const data2 = await res2.json();
const products = data2; // ← bu yerda to'g'ridan-to'g'RI massiv?
const res3 = await fetch('/api/orders');
const data3 = await res3.json();
const orders = data3.data; // ← bu yerda .data
Standart format bo'lsa — bir marta yozilgan kod hamma yerda ishlaydi.
Standart format
Eng keng tarqalgan yondashuv:
{
"success": true,
"data": { ... },
"message": "Muvaffaqiyatli",
"meta": {
"page": 1,
"total": 100
}
}
Xato uchun:
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Foydalanuvchi topilmadi"
}
}
Javob yordamchi funksiyalari
Har endpointda bir xil strukturani qo'lda yozish o'rniga — yordamchi funksiyalar:
// utils/response.js
const successResponse = (res, data, message = 'Muvaffaqiyatli', statusCode = 200) => {
return res.status(statusCode).json({
success: true,
message,
data,
});
};
const createdResponse = (res, data, message = 'Yaratildi') => {
return successResponse(res, data, message, 201);
};
const errorResponse = (res, message, statusCode = 400, code = null) => {
return res.status(statusCode).json({
success: false,
error: {
code: code || statusCodeToErrorCode(statusCode),
message,
},
});
};
const notFoundResponse = (res, message = 'Topilmadi') => {
return errorResponse(res, message, 404, 'NOT_FOUND');
};
const unauthorizedResponse = (res, message = 'Autentifikatsiya kerak') => {
return errorResponse(res, message, 401, 'UNAUTHORIZED');
};
const forbiddenResponse = (res, message = 'Ruxsat yo\'q') => {
return errorResponse(res, message, 403, 'FORBIDDEN');
};
const validationErrorResponse = (res, errors) => {
return res.status(422).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Kiritilgan ma\'lumotlarda xato',
details: errors, // ← qaysi maydon, qanday xato
},
});
};
// Ichki yordamchi
const statusCodeToErrorCode = (code) => {
const map = { 400: 'BAD_REQUEST', 401: 'UNAUTHORIZED', 403: 'FORBIDDEN',
404: 'NOT_FOUND', 409: 'CONFLICT', 500: 'SERVER_ERROR' };
return map[code] || 'ERROR';
};
module.exports = {
successResponse,
createdResponse,
errorResponse,
notFoundResponse,
unauthorizedResponse,
forbiddenResponse,
validationErrorResponse,
};
Endpointlarda ishlatish
const {
successResponse,
createdResponse,
notFoundResponse,
validationErrorResponse,
} = require('../utils/response');
// GET — barcha mahsulotlar
router.get('/', async (req, res, next) => {
try {
const products = await Product.find();
return successResponse(res, products);
// → { success: true, message: "Muvaffaqiyatli", data: [...] }
} catch (err) {
next(err);
}
});
// GET — bitta mahsulot
router.get('/:id', async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return notFoundResponse(res, 'Mahsulot topilmadi');
// → { success: false, error: { code: "NOT_FOUND", message: "..." } }
}
return successResponse(res, product);
} catch (err) {
next(err);
}
});
// POST — yangi mahsulot
router.post('/', async (req, res, next) => {
try {
const { name, price } = req.body;
// Validatsiya
const errors = [];
if (!name) errors.push({ field: 'name', message: 'Nom majburiy' });
if (!price) errors.push({ field: 'price', message: 'Narx majburiy' });
if (price < 0) errors.push({ field: 'price', message: 'Narx manfiy bo\'lmasligi kerak' });
if (errors.length > 0) {
return validationErrorResponse(res, errors);
// → { success: false, error: { code: "VALIDATION_ERROR", details: [...] } }
}
const product = await Product.create({ name, price });
return createdResponse(res, product, 'Mahsulot yaratildi');
// → { success: true, message: "Mahsulot yaratildi", data: {...} }
} catch (err) {
next(err);
}
});
Pagination uchun meta
Ro'yxat endpointlarida sahifalash ma'lumoti ham kerak:
// utils/response.js ga qo'shimcha
const paginatedResponse = (res, data, meta) => {
return res.status(200).json({
success: true,
data,
meta: {
page: meta.page,
limit: meta.limit,
total: meta.total,
totalPages: Math.ceil(meta.total / meta.limit),
hasNextPage: meta.page < Math.ceil(meta.total / meta.limit),
hasPrevPage: meta.page > 1,
},
});
};
router.get('/', async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const [products, total] = await Promise.all([
Product.find().skip(skip).limit(limit),
Product.countDocuments(),
]);
return paginatedResponse(res, products, { page, limit, total });
/*
{
"success": true,
"data": [...],
"meta": {
"page": 1,
"limit": 10,
"total": 85,
"totalPages": 9,
"hasNextPage": true,
"hasPrevPage": false
}
}
*/
} catch (err) {
next(err);
}
});
Global xato handler bilan integratsiya
// middleware/errorHandler.js
const { errorResponse } = require('../utils/response');
const errorHandler = (err, req, res, next) => {
console.error(err);
// Mongoose validatsiya xatosi
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(e => ({
field: e.path,
message: e.message,
}));
return res.status(422).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Validatsiya xatosi',
details: errors,
},
});
}
// Mongoose CastError — noto'g'ri ID format
if (err.name === 'CastError') {
return errorResponse(res, 'Noto\'g\'ri ID format', 400, 'INVALID_ID');
}
// MongoDB duplicate key
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
return errorResponse(res, `${field} allaqachon mavjud`, 409, 'DUPLICATE');
}
// JWT xatolari
if (err.name === 'JsonWebTokenError') {
return errorResponse(res, 'Noto\'g\'ri token', 401, 'INVALID_TOKEN');
}
if (err.name === 'TokenExpiredError') {
return errorResponse(res, 'Token muddati o\'tgan', 401, 'TOKEN_EXPIRED');
}
// Umumiy server xatosi
return errorResponse(res, 'Serverda xato yuz berdi', 500, 'SERVER_ERROR');
};
module.exports = errorHandler;
// app.js
const errorHandler = require('./middleware/errorHandler');
app.use('/api/v1', require('./routes/v1'));
app.use(errorHandler); // ← eng oxirida
Frontend da bir marta yozilgan kod
Standart format bo'lsa frontend ham soddalashadi:
// frontend/api.js — universal so'rov funksiyasi
const apiRequest = async (url, options = {}) => {
const res = await fetch(url, {
headers: { 'Content-Type': 'application/json' },
...options,
});
const json = await res.json();
if (!json.success) {
// Xato kodi bo'yicha qaror
if (json.error.code === 'UNAUTHORIZED') {
window.location.href = '/login';
return;
}
throw new Error(json.error.message);
}
return json.data; // ← har doim .data
};
// Ishlatish — hamma endpoint bir xil
const users = await apiRequest('/api/v1/users');
const product = await apiRequest('/api/v1/products/5');
const newOrder = await apiRequest('/api/v1/orders', {
method: 'POST',
body: JSON.stringify({ productId: 5, qty: 2 }),
});
Tez-tez uchraydigan xatolar
1. success: true bilan xato status kodi
// ❌
res.status(404).json({ success: true, data: null });
Sababi: success: true va 404 ziddiyatli. Frontend status kodga ham, success ga ham qaraydi — ikkalasi mos kelishi kerak.
2. Har xil maydon nomlaridan foydalanish
// ❌ — ba'zi endpointlarda result, ba'zilarida data
res.json({ result: users });
res.json({ data: products });
res.json({ items: orders });
Sababi: frontend data kutadi, lekin result keladi — undefined. Butun loyiha bo'yicha bir xil nom ishlatilsin.
3. Xato ma'lumotini foydalanuvchiga ko'rsatish
// ❌ — server ichki xatosi foydalanuvchiga ko'rinadi
res.status(500).json({ error: err.message });
// → "Cannot read properties of undefined (reading 'id')"
// ✅ — foydalanuvchiga umumiy xabar, log da batafsil
console.error(err);
res.status(500).json({ success: false, error: { code: 'SERVER_ERROR', message: 'Serverda xato yuz berdi' } });
Sababi: ichki xato xabarlari dastur tuzilishi haqida ma'lumot beradi — xavfsizlik xatari.
Xulosa
- Standart format —
{ success, data, message, meta }— hamma endpoint bir xil - Yordamchi funksiyalar —
successResponse,errorResponse,notFoundResponse— takrorlanishni kamaytiradi - Xato kodlari —
NOT_FOUND,UNAUTHORIZED,VALIDATION_ERROR— frontend qaror qilish uchun - Global xato handler — Mongoose, JWT xatolarini avtomatik formatlaydi
- Frontend bir marta yozilgan kod — hamma endpoint uchun ishlaydi
Keyingi dars: 38-dars: Pagination — offset va cursor based