Filtering va Sorting — query params bilan
Muammodan boshlaylik
Foydalanuvchi mahsulotlar ro'yxatini ko'ryapti. U istaydi:
- Faqat elektronika kategoriyasidagi mahsulotlar
- Narxi 50,000 dan 200,000 gacha
- Narz bo'yicha arzondan qimmatlga tartiblangan
Buning uchun alohida endpoint yozish — noto'g'ri:
// ❌ — har holat uchun alohida endpoint
app.get('/products/elektronika', ...);
app.get('/products/elektronika/cheap', ...);
app.get('/products/elektronika/expensive', ...);
// ... cheksiz kombinatsiyalar
To'g'ri yondashuv: bitta endpoint, query params orqali filter va sort:
GET /products?category=elektronika&minPrice=50000&maxPrice=200000&sort=price&order=asc
Arxitektura
Filtr va sort logikasini route ichiga yozmaslik kerak. Uni alohida funksiyaga ajratamiz:
routes/
products.js ← faqat route va response
utils/
buildQuery.js ← filter + sort logikasi
Bu nima beradi? Route toza qoladi. Logika qayta ishlatiladi. Test qilish oson.
1-qadam: buildQuery yordamchi funksiyasi
// utils/buildQuery.js
const buildQuery = (queryParams) => {
const { category, minPrice, maxPrice, search } = queryParams;
const filter = {}; // ← Mongoose ga beriladigan filter
// Kategoriya bo'yicha
if (category) {
filter.category = category; // ← { category: "elektronika" }
}
// Narx oralig'i
if (minPrice || maxPrice) {
filter.price = {};
if (minPrice) filter.price.$gte = Number(minPrice); // ← kattaroq yoki teng
if (maxPrice) filter.price.$lte = Number(maxPrice); // ← kichik yoki teng
}
// Matn qidirish
if (search) {
filter.name = { $regex: search, $options: 'i' }; // ← katta-kichik harfsiz
}
return filter;
};
module.exports = buildQuery;
2-qadam: buildSort yordamchi funksiyasi
// utils/buildSort.js
const ALLOWED_SORT_FIELDS = ['price', 'name', 'createdAt']; // ← faqat shu maydonlar
const buildSort = (queryParams) => {
const { sort = 'createdAt', order = 'desc' } = queryParams;
// Ruxsatsiz maydonga sort qilishni oldini olish
const sortField = ALLOWED_SORT_FIELDS.includes(sort) ? sort : 'createdAt';
const sortOrder = order === 'asc' ? 1 : -1; // ← Mongoose: 1 yoki -1
return { [sortField]: sortOrder };
};
module.exports = buildSort;
Nima uchun ALLOWED_SORT_FIELDS? Foydalanuvchi sort=password yuborsa nima bo'ladi? Yoki sort=__v? Faqat ruxsat etilgan maydonlar ishlashi kerak.
3-qadam: Route — toza va sodda
// routes/products.js
const express = require('express');
const router = express.Router();
const Product = require('../models/Product');
const buildQuery = require('../utils/buildQuery');
const buildSort = require('../utils/buildSort');
router.get('/', async (req, res, next) => {
try {
const filter = buildQuery(req.query); // ← filter logikasi tashqarida
const sort = buildSort(req.query); // ← sort logikasi tashqarida
const products = await Product.find(filter).sort(sort);
res.json({ success: true, data: products });
} catch (err) {
next(err);
}
});
module.exports = router;
Route ichida hech qanday if yo'q — toza.
Pagination bilan birga
// routes/products.js
const buildQuery = require('../utils/buildQuery');
const buildSort = require('../utils/buildSort');
router.get('/', async (req, res, next) => {
try {
const filter = buildQuery(req.query);
const sort = buildSort(req.query);
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, parseInt(req.query.limit) || 10);
const skip = (page - 1) * limit;
const [products, total] = await Promise.all([
Product.find(filter).sort(sort).skip(skip).limit(limit),
Product.countDocuments(filter), // ← filtrlangan umumiy son
]);
res.json({
success: true,
data: products,
meta: {
page, limit, total,
totalPages: Math.ceil(total / limit),
},
});
} catch (err) {
next(err);
}
});
So'rovlar misoli:
GET /products
GET /products?category=elektronika
GET /products?minPrice=50000&maxPrice=200000
GET /products?search=iphone
GET /products?sort=price&order=asc
GET /products?category=elektronika&sort=price&order=asc&page=2&limit=20
Hammasi bitta endpoint — kombinatsiyalar cheksiz.
SQL (PostgreSQL) da xuddi shu yondashuv
// utils/buildSqlQuery.js
const buildSqlQuery = (queryParams) => {
const { category, minPrice, maxPrice, search } = queryParams;
const conditions = []; // ← WHERE shartlari
const values = []; // ← parametrlar (injection himoya)
if (category) {
values.push(category);
conditions.push(`category = ${values.length}`);
}
if (minPrice) {
values.push(Number(minPrice));
conditions.push(`price >= ${values.length}`);
}
if (maxPrice) {
values.push(Number(maxPrice));
conditions.push(`price <= ${values.length}`);
}
if (search) {
values.push(`%${search}%`);
conditions.push(`name ILIKE ${values.length}`); // ← katta-kichik harfsiz
}
const where = conditions.length > 0
? 'WHERE ' + conditions.join(' AND ')
: '';
return { where, values };
};
module.exports = buildSqlQuery;
// routes/products.js (SQL versiya)
const buildSqlQuery = require('../utils/buildSqlQuery');
router.get('/', async (req, res, next) => {
try {
const { where, values } = buildSqlQuery(req.query);
const { sort = 'created_at', order = 'desc' } = req.query;
const ALLOWED = ['price', 'name', 'created_at'];
const sortField = ALLOWED.includes(sort) ? sort : 'created_at';
const sortOrder = order === 'asc' ? 'ASC' : 'DESC';
const query = `SELECT * FROM products ${where} ORDER BY ${sortField} ${sortOrder}`;
const result = await pool.query(query, values);
res.json({ success: true, data: result.rows });
} catch (err) {
next(err);
}
});
To'liq papka tuzilishi
project/
├── routes/
│ └── products.js ← GET / endpoint, response
├── utils/
│ ├── buildQuery.js ← MongoDB filter
│ ├── buildSort.js ← sort logikasi
│ └── response.js ← success/error yordamchilari (37-dars)
├── models/
│ └── Product.js
└── app.js
Tez-tez uchraydigan xatolar
1. Number ga o'tkazmaslik
// ❌ — query params har doim string bo'lib keladi
filter.price = { $gte: req.query.minPrice }; // ← "50000" (string)
// MongoDB string va raqamni taqqoslaydi — noto'g'ri natija
// ✅
filter.price = { $gte: Number(req.query.minPrice) }; // ← 50000 (number)
2. Sort maydonini tekshirmaslik
// ❌ — foydalanuvchi istalgan maydonga sort qila oladi
const sort = { [req.query.sort]: -1 };
// ✅ — whitelist bilan
const ALLOWED = ['price', 'name', 'createdAt'];
const sortField = ALLOWED.includes(req.query.sort) ? req.query.sort : 'createdAt';
3. Bo'sh search bilan regex
// ❌ — search="" bo'lsa, barcha yozuvlarni regex bilan skanerlaydi, sekin
if (search) {
filter.name = { $regex: search, $options: 'i' };
}
// search = "" ham truthy... yo'q, aslida "" falsy. Lekin " " (bo'shliq) truthy
// ✅ — trim() bilan tozalash
const search = req.query.search?.trim();
if (search) {
filter.name = { $regex: search, $options: 'i' };
}
Xulosa
- Filter va sort logikasini route dan ajrating —
buildQuery.js,buildSort.js - Route toza qoladi:
find(filter).sort(sort)— ikki qator - Sort maydonlarini whitelist bilan tekshiring — xavfsizlik
- Query params string bo'lib keladi —
Number()bilan o'tkazing - Pagination bilan birga:
countDocuments(filter)— filtrlangan umumiy son
Keyingi dars: 40-dars: API dokumentatsiya — Swagger bilan