[NODE JS] Express ile Arama ve Filtreleme
Kullanıcılar veri okyanusunda iğne aramak istemez. Etkili arama ve filtreleme mekanizmaları sunmak iyi bir API'nin olmazsa olmazıdır. Arama genellikle text bazlı serbest arama yaparken, filtreleme belirli kriterlerle sonuçları daraltır. İkisi bir arada kullanıldığında çok güçlü bir veri keşif deneyimi sunar.
Full-text search basit string matching'den çok daha karmaşıktır. Türkçe gibi dillerde büyük-küçük harf dönüşümü, Türkçe karakterler ve kelime köklerine ayırma (stemming) gibi konular devreye girer. I harfinin büyük hali İ, i harfinin büyük hali İ olmalıdır. Bu dönüşümleri doğru yapmak arama kalitesini doğrudan etkiler.
Ben bir e-ticaret projesinde arama özelliği geliştirirken JavaScript'in toLowerCase() fonksiyonunun Türkçe karakterlerle sorun çıkardığını fark ettim. "İSTANBUL" kelimesini küçültünce "i̇stanbul" oluyordu. toLocaleLowerCase('tr-TR') kullanarak bu sorunu çözdüm. Uluslararasılaştırma konuları arama geliştirirken çok önemli.
Filtreleme mekanizması genellikle query parametreleri üzerinden çalışır. Kategori, fiyat aralığı, tarih aralığı, boolean değerler gibi kriterlerle sonuçları daraltırsınız. Birden fazla filtreyi bir arada kullanabilmek önemli: hem kategorisi elektronik, hem fiyatı 1000-5000 arası, hem puanı 4 üzeri olan ürünleri getirmek gibi.
Faceted search e-ticaret sitelerinde çok yaygın. Filtreleme seçeneklerinin yanında her seçeneğin kaç sonuç vereceğini gösterir. Mesela "Elektronik (45), Giyim (23), Kitap (12)" gibi. Bu kullanıcıya hangi filtrelerin anlamlı sonuç vereceğini gösterir. Ama hesaplaması performans açısından maliyetli olabilir.
Fuzzy search kullanıcının yazım hatalarını tolere eden arama türüdür. "laptp" yazsa bile "laptop" sonuçlarını gösterebilir. Levenshtein distance algoritması iki string arasındaki düzenleme mesafesini hesaplar. Basit uygulamalar için yeterli olsa da büyük veri setlerinde Elasticsearch gibi özel arama motorları kullanmak daha mantıklı.
Arama sonuçlarını sıralamak da önemli. Relevance bazlı sıralama arama terimine en çok uyan sonuçları üstte gösterir. Popularity, date veya price gibi alternatif sıralamalar da sunulmalıdır. Kullanıcıya sıralama seçeneği vermek iyi bir pratik.
Debounce ile arama performansını artırabilirsiniz. Kullanıcı her tuşa bastığında API isteği göndermek yerine, yazmayı bıraktıktan 300-500ms sonra istek göndermek sunucu yükünü önemli ölçüde azaltır. Bu frontend tarafında yapılır ama backend'de de rate limiting uygulamak faydalıdır.
JavaScript:
const express = require('express');
const app = express();
// Ornek veri seti
const urunler = [
{{ id: 1, ad: 'iPhone 15 Pro', marka: 'Apple', kategori: 'telefon', fiyat: 65000, puan: 4.8, stok: true, renk: ['siyah', 'beyaz', 'mavi'], ozellikler: ['5G', 'Face ID', 'USB-C'] }},
{{ id: 2, ad: 'Samsung Galaxy S24 Ultra', marka: 'Samsung', kategori: 'telefon', fiyat: 55000, puan: 4.7, stok: true, renk: ['siyah', 'gri'], ozellikler: ['5G', 'S-Pen', 'AI'] }},
{{ id: 3, ad: 'MacBook Pro M3', marka: 'Apple', kategori: 'bilgisayar', fiyat: 85000, puan: 4.9, stok: true, renk: ['gri', 'gumus'], ozellikler: ['M3 chip', 'Retina'] }},
{{ id: 4, ad: 'Dell XPS 15', marka: 'Dell', kategori: 'bilgisayar', fiyat: 45000, puan: 4.5, stok: true, renk: ['gumus'], ozellikler: ['Intel i7', 'OLED'] }},
{{ id: 5, ad: 'AirPods Pro 2', marka: 'Apple', kategori: 'aksesuar', fiyat: 8500, puan: 4.7, stok: false, renk: ['beyaz'], ozellikler: ['ANC', 'Spatial Audio'] }},
{{ id: 6, ad: 'Sony WH-1000XM5', marka: 'Sony', kategori: 'aksesuar', fiyat: 12000, puan: 4.8, stok: true, renk: ['siyah', 'gumus'], ozellikler: ['ANC', 'LDAC'] }},
{{ id: 7, ad: 'iPad Air M2', marka: 'Apple', kategori: 'tablet', fiyat: 25000, puan: 4.6, stok: true, renk: ['mavi', 'mor', 'beyaz'], ozellikler: ['M2 chip', 'USB-C'] }},
{{ id: 8, ad: 'Pixel 8 Pro', marka: 'Google', kategori: 'telefon', fiyat: 38000, puan: 4.4, stok: true, renk: ['siyah', 'mavi'], ozellikler: ['5G', 'AI', 'Tensor G3'] }},
{{ id: 9, ad: 'ThinkPad X1 Carbon', marka: 'Lenovo', kategori: 'bilgisayar', fiyat: 52000, puan: 4.6, stok: true, renk: ['siyah'], ozellikler: ['Intel i7', 'Thunderbolt'] }},
{{ id: 10, ad: 'Galaxy Tab S9', marka: 'Samsung', kategori: 'tablet', fiyat: 22000, puan: 4.3, stok: true, renk: ['siyah', 'bej'], ozellikler: ['S-Pen', 'AMOLED'] }},
];
// Turkce uyumlu arama
function turkceArama(metin, aranan) {{
const normalizetr = (s) => s.toLocaleLowerCase('tr-TR');
return normalizetr(metin).includes(normalizetr(aranan));
}}
// GET /api/urunler/ara?q=iphone&kategori=telefon&minFiyat=10000&maxFiyat=70000&marka=Apple,Samsung&stok=true&siralama=fiyat&sira=asc
app.get('/api/urunler/ara', (req, res) => {{
let sonuc = [...urunler];
const filtreler = {{}};
// Text arama
if (req.query.q) {{
const q = req.query.q;
sonuc = sonuc.filter(u =>
turkceArama(u.ad, q) ||
turkceArama(u.marka, q) ||
turkceArama(u.kategori, q) ||
u.ozellikler.some(o => turkceArama(o, q))
);
filtreler.arama = q;
}}
// Kategori filtresi
if (req.query.kategori) {{
const kategoriler = req.query.kategori.split(',');
sonuc = sonuc.filter(u => kategoriler.includes(u.kategori));
filtreler.kategori = kategoriler;
}}
// Marka filtresi
if (req.query.marka) {{
const markalar = req.query.marka.split(',');
sonuc = sonuc.filter(u => markalar.includes(u.marka));
filtreler.marka = markalar;
}}
// Fiyat araligi
if (req.query.minFiyat) {{
sonuc = sonuc.filter(u => u.fiyat >= Number(req.query.minFiyat));
filtreler.minFiyat = Number(req.query.minFiyat);
}}
if (req.query.maxFiyat) {{
sonuc = sonuc.filter(u => u.fiyat <= Number(req.query.maxFiyat));
filtreler.maxFiyat = Number(req.query.maxFiyat);
}}
// Puan filtresi
if (req.query.minPuan) {{
sonuc = sonuc.filter(u => u.puan >= Number(req.query.minPuan));
}}
// Stok filtresi
if (req.query.stok === 'true') sonuc = sonuc.filter(u => u.stok);
if (req.query.stok === 'false') sonuc = sonuc.filter(u => !u.stok);
// Renk filtresi
if (req.query.renk) {{
const renkler = req.query.renk.split(',');
sonuc = sonuc.filter(u => u.renk.some(r => renkler.includes(r)));
}}
// Faceted counts (filtre uygulanmadan onceki sayilar)
const facets = {{
kategoriler: {{}},
markalar: {{}},
renkler: {{}},
}};
sonuc.forEach(u => {{
facets.kategoriler[u.kategori] = (facets.kategoriler[u.kategori] || 0) + 1;
facets.markalar[u.marka] = (facets.markalar[u.marka] || 0) + 1;
u.renk.forEach(r => {{ facets.renkler[r] = (facets.renkler[r] || 0) + 1; }});
}});
// Siralama
const sort = req.query.siralama || 'puan';
const order = req.query.sira === 'asc' ? 1 : -1;
sonuc.sort((a, b) => (a[sort] > b[sort] ? order : -order));
// Sayfalama
const sayfa = Math.max(1, parseInt(req.query.sayfa) || 1);
const limit = Math.min(50, parseInt(req.query.limit) || 10);
const toplam = sonuc.length;
sonuc = sonuc.slice((sayfa - 1) * limit, sayfa * limit);
res.json({{
veri: sonuc,
filtreler,
facets,
sayfalama: {{ toplam, sayfa, limit, toplamSayfa: Math.ceil(toplam / limit) }},
}});
}});
app.listen(3000, () => console.log('Arama sunucusu port 3000'));
Arama ve filtreleme kullanıcı memnuniyetini doğrudan etkiler. Hızlı, doğru ve esnek bir arama deneyimi sunmak rekabet avantajı sağlar. Başlangıçta basit bir implementasyonla başlayıp zamanla Elasticsearch gibi araçlarla güçlendirebilirsiniz.
Emeğiniz ve katkınız için teşekkür ederiz!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar!

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)
Benzer konular
- Cevaplar
- 2
- Görüntüleme
- 46
- Cevaplar
- 20
- Görüntüleme
- 355
- Cevaplar
- 9
- Görüntüleme
- 683
- Kilitli
Altın Konu
#Orion2 55-120 Files DEV PAYLAŞIM
- Cevaplar
- 125
- Görüntüleme
- 16K
- Cevaplar
- 0
- Görüntüleme
- 605