Node.js'te Redis için önerilen istemci node-redis'tir: npm install redis ile kurar, createClient() ile istemci oluşturur, mutlaka bir error dinleyicisi ekler ve await client.connect() ile bağlanırsınız. Ardından await client.set('key', 'value') ve await client.get('key') gibi Promise döndüren metotlarla çalışırsınız. Belgeler hata dinleyicisinin isteğe bağlı olmadığını özellikle vurgular: dinleyici yoksa istemcide oluşan bir hata fırlatılır ve Node.js sürecini beklenmedik biçimde sonlandırabilir.
Bu yazıda node-redis ile bağlantıyı, bağlantı olaylarını ve yeniden bağlanma stratejisini, otomatik pipelining ile multi() kullanımını, WATCH için neden bağlantı havuzu gerektiğini, istemci tarafı önbelleği, hata kodlarını ve ioredis'ten geçişte değişen noktaları resmî belgelere dayanarak anlatıyoruz. Yazı JavaScript dilini değil, Redis kütüphanesini konu alıyor; dil tarafı için Node.js öğrenme rehberi yazısına bakabilirsiniz. Dile bağlı olmayan URL kuralları Redis bağlantı URL ve connection pool yazısında, serinin geneli Redis öğrenme rehberi sayfasında.
node-redis kurulumu ve ilk bağlantı
npm install redisnode-redis'in ioredis'ten belirgin bir farkı, istemci nesnesini oluşturmanın bağlantıyı başlatmamasıdır; bağlantı için connect() metodunu açıkça çağırmanız gerekir. Belgelerdeki temel örnek şöyledir:
import { createClient } from 'redis';
const client = createClient();
client.on('error', err => console.log('Redis Client Error', err));
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');Bir nesneyi hash olarak saklamak için hSet(), okumak için hGetAll() kullanılır. node-redis komut adlarını büyük harfle (HSET) ya da camelCase biçimiyle (hSet) kabul eder. Belgelerdeki çıktıda age alanının metin olarak döndüğünü görürsünüz; Redis hash alanlarını her zaman metin olarak saklar.
await client.hSet('user-session:123', {
name: 'John',
surname: 'Smith',
company: 'Redis',
age: 29
})
let userSession = await client.hGetAll('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/*
{
"surname": "Smith",
"name": "John",
"company": "Redis",
"age": "29"
}
*/Başka bir sunucuya ya da porta bağlanmak için url seçeneği kullanılır: createClient({ url: 'redis://alice:foobared@awesome.redis.server:6380' }). node-redis redis:// ve rediss:// URL'lerindeki sorgu parametrelerini yok sayar; RESP sürümü gibi ayarları seçenek nesnesinde vermeniz gerekir. URL'deki kullanıcı adı, şifre ve veritabanı ise seçeneklerdeki aynı alanları geçersiz kılar.
İstemcinin komut göndermeye hazır olup olmadığını client.isReady ile kontrol edersiniz. client.isOpen ise yalnızca alttaki soketin açık olup olmadığını söyler; istemci hâlâ bağlanırken ya da bir ağ hatasından sonra yeniden bağlanırken false döner. İşiniz bittiğinde bağlantıyı client.close() ile kapatırsınız; belgelerdeki TLS örneği ise client.destroy() kullanır.
Bağlantı olayları ve error dinleyicisi

İstemci nesnesi bağlantıyla ilgili şu olayları yayar:
connect: istemci sunucuya bağlanmaya başlamak üzere.ready: bağlantı kuruldu, istemci kullanıma hazır.end: istemciclient.close()ile bilerek kapatıldı.error: bir hata oluştu; genellikle "Socket closed unexpectedly" gibi bir ağ sorunudur ve olay birErrornesnesiyle gelir.reconnecting: bağlantı bir hata yüzünden koptu, istemci yeniden bağlanmayı denemek üzere.sharded-channel-moved: abone olunan sharded pub/sub kanalının cluster slotu başka bir parçaya taşındı.
Üretimde bu olaylardan en kritik olanı error'dır. Node.js'in EventEmitter davranışı gereği, hiç error dinleyicisi olmayan bir nesnede hata olayı oluşursa hata fırlatılır. Belgelerin önerdiği en küçük güvenli kurulum şudur:
const client = createClient({
// ... client options
});
// Always ensure there's a listener for errors in the client to prevent process crashes due to unhandled errors
client.on('error', error => {
console.error(`Redis client error:`, error);
});Yeniden bağlanma stratejisi ve zaman aşımları
Soket close() veya destroy() çağrılmadan beklenmedik biçimde kapanırsa node-redis bağlantıyı kendiliğinden geri kurar. Varsayılan strateji, istemcilerin aynı anda yeniden denemesini önlemek için rastgele sapma eklenmiş üstel geri çekilmedir. Bunu soket yapılandırmasındaki reconnectStrategy alanıyla değiştirebilirsiniz. Alan false (yeniden bağlanma), bir sayı (o kadar milisaniye bekleyip dene) ya da bir fonksiyon alır. Fonksiyonun imzası (retries, cause) => false | number | Error biçimindedir: her denemeden önce o ana kadarki deneme sayısı ve bağlantının neden koptuğunu anlatan hata nesnesiyle çağrılır. Error döndürmek false ile aynı etkiyi yapar ama neden yeniden bağlanılmadığına dair bilgi ekler. Belgelerdeki örnek, üst sınırı 3000 ms olan özel bir üstel geri çekilme uygular:
createClient({
socket: {
reconnectStrategy: retries => {
// Generate a random jitter between 0 and 100 ms:
const jitter = Math.floor(Math.random() * 100);
// Delay is an exponential backoff, (2^retries) * 50 ms, with a
// maximum value of 3000 ms:
const delay = Math.min(Math.pow(2, retries) * 50, 3000);
return delay + jitter;
}
}
});Bağlantı kurma zaman aşımı socket.connectTimeout ile ayarlanır; varsayılan değer 5 saniyedir. Tek tek komutlara süre sınırı koymak için ise AbortController ve commandOptions kullanılır:
import { createClient, commandOptions } from 'redis';
const client = createClient({
url: 'redis://localhost:6379',
socket: { connectTimeout: 10000 } // in milliseconds
});
client.on('error', error => console.error('Redis client error:', error));
await client.connect();
const ac = new AbortController();
const t = setTimeout(() => ac.abort(), 1000);
try {
const val = await client.get(commandOptions({ signal: ac.signal }), key);
} finally {
clearTimeout(t);
}Çevrimdışı kuyruk ve idempotent olmayan komutlar
Bağlantı koptuğunda çalıştırdığınız komutlar varsayılan olarak bir kuyrukta bekletilir ve bağlantı geri geldiğinde sunucuya gönderilir. Belgeler bunun bir riskini açıklar: bağlantı tam bir komut yürütülürken koparsa, komut sunucudaki veriyi değiştirmiş olabilir ama istemci onu kuyruktan çıkarmamış olabilir. Bağlantı geri geldiğinde komut yeniden gönderilir. INCR gibi idempotent olmayan bir komutta bu, sayacın iki kez artması demektir. Bunu önlemek için istemciyi disableOfflineQueue: true ile oluşturursunuz; bu durumda çalıştırılamayan komutlar kuyruğa alınmaz, atılır. Belgeler yalnızca belirli komutlar için kuyruğu kapatmak istiyorsanız kuyruğu kapalı ayrı bir bağlantı kullanmanızı önerir.
node-redis ile pipeline

node-redis, olay döngüsünün aynı turunda (tick) çalışan komutları kendiliğinden tek bir pipeline'da gönderir. Komutların aynı turda çalıştığından emin olmanın en kolay yolu onları Promise.all() içine koymaktır. Belgelerdeki örnekte üç SET ve üç GET bu şekilde gönderilir:
await Promise.all([
client.set('seat:0', '#0'),
client.set('seat:1', '#1'),
client.set('seat:2', '#2'),
]).then((results) =>{
console.log(results);
// >>> ['OK', 'OK', 'OK']
});
await Promise.all([
client.get('seat:0'),
client.get('seat:1'),
client.get('seat:2'),
]).then((results) =>{
console.log(results);
// >>> ['#0', '#1', '#2']
});Açık bir pipeline nesnesi isterseniz multi() ile oluşturur, komutları zincirler ve transaction yerine pipeline olarak çalıştırmak için execAsPipeline() çağırırsınız:
await client.multi()
.set('seat:3', '#3')
.set('seat:4', '#4')
.set('seat:5', '#5')
.execAsPipeline()
.then((results) => {
console.log(results);
// >>> ['OK', 'OK', 'OK']
});İki yöntem neredeyse aynıdır ama bağlantı pipeline'ın ortasında koparsa farklı davranırlar. Bağlantı geri geldiğinde Promise.all() ile gönderilen pipeline kaldığı yerden devam eder; multi() ile kurulan pipeline ise henüz çalışmamış komutları atar. Kısmen yürütülmüş bir topluluğun devam etmesi sizin için sorunluysa ikinci yöntemi tercih edin.
multi() ve exec() ile transaction
Transaction da aynı multi() nesnesiyle kurulur; fark, sonda exec() çağrılmasıdır. Bu durumda komutlar sunucuda MULTI/EXEC içinde, başka istemcilerin komutlarıyla araya girilmeden yürütülür. exec() sonuçları bir dizi olarak döndürür:
const [res1, res2, res3] = await client.multi()
.incrBy("counter:1", 1)
.incrBy("counter:2", 2)
.incrBy("counter:3", 3)
.exec();
console.log(res1); // >>> 1
console.log(res2); // >>> 2
console.log(res3); // >>> 3Belgelerdeki oturum deposu örneği, hash'i ve süresini tek transaction'da yazarak anahtarın süresiz kalmasını engeller: await this.redis.multi().hSet(key, payload).expire(key, sessionTtl).exec();. Sunucu tarafında transaction'ların hata türlerini ve geri alma yapılmamasını MULTI, EXEC ve WATCH yazısında, pipelining'in genel mantığını Redis pipelining yazısında anlatıyoruz.
WATCH ile iyimser kilit ve neden havuz gerekir
İyimser kilitte izlenen anahtarı okurken komutları normal client nesnesinde, transaction komutlarını ise multi() ile oluşturulan nesnede çalıştırırsınız. İzlenen anahtar yazmadan önce başka bir istemci tarafından değiştirilirse transaction iptal edilir ve Promise reddedilir. Belgeler üretimde bu kodun bir döngü içinde, başarılı olana kadar ya da hatayı kaydedene kadar yeniden çalıştırılmasını önerir:
// Set initial value of `shellpath`.
await client.set('shellpath', '/usr/syscmds/');
// Watch the key we are about to update.
await client.watch('shellpath');
const currentPath = await client.get('shellpath');
const newPath = currentPath + ':/usr/mycmds/';
await client.multi()
.set('shellpath', newPath)
.exec()
.then((result) => {
console.log(result);
}, (err) => {
// This is called when a watched key was changed.
console.log(err);
});
const updatedPath = await client.get('shellpath');
console.log(updatedPath);
// >>> /usr/syscmds/:/usr/mycmds/Burada çok önemli bir ayrıntı var: sunucu WATCH durumunu bağlantı başına tutar. Web sunucusu gibi birçok eşzamanlı isteğin tek bir bağlantıyı paylaştığı bir ortamda, aynı bağlantı üzerindeki eşzamanlı WATCH ve MULTI/EXEC çağrıları birbirini bozar. Belgeler bu durumda yalıtılmış bir bağlantı almak için bağlantı havuzu kullanmanız gerektiğini söyler. Havuzun execute() metodu fonksiyonunuza yalnızca o işe ayrılmış bir istemci verir; transaction iptal edilirse WatchError yakalanır:
import { createClientPool } from 'redis';
const pool = await createClientPool()
.on('error', err => console.error('Redis Client Pool Error', err));
try {
await pool.execute(async client => {
await client.watch('key');
const multi = client.multi()
.ping()
.get('key');
return multi.exec();
});
} catch (err) {
if (err instanceof WatchError) {
// the transaction aborted
}
}Cluster, Sentinel ve TLS
Redis Cluster için createCluster() kullanılır ve rootNodes içinde her düğüm için bir url verilir. Belgelere göre bu URL'lerdeki kimlik bilgileri yalnızca ilk bağlantılarda kullanılır; diğer düğümler için kimlik bilgilerini defaults seçeneğinde vermeniz gerekir. Sentinel için createSentinel() vardır: name alanına izlenen birincil sunucunun adını, sentinelRootNodes içine Sentinel adreslerini yazarsınız; istemci yük devretmeden sonra yeni birincile geçer. Sentinel kimlik bilgileri sentinelClientOptions, veri düğümlerininki nodeClientOptions içinde verilir ve bu seçenekler url kabul etmez. Okumaları kopyalara göndermek için replicaPoolSize ayarlanır; replikasyon asenkron olduğu için yazmanın hemen ardından yapılan okuma eski veri döndürebilir. TLS bağlantısında ise socket nesnesine tls: true ile birlikte key, cert ve ca alanları eklenir.
İstemci tarafı önbellek
node-redis 5.1.0 ve sonrası istemci tarafı önbelleği destekler; en geniş uyumluluk için sunucu tarafında Redis 7.4 veya üstü önerilir. Özelliği açmak için RESP3 protokolünü seçip clientSideCache parametresini verirsiniz. Önbelleğe doğrudan erişmek isterseniz kendi BasicClientSideCache nesnenizi oluşturup verebilirsiniz; bu nesnenin invalidate(), clear() ve stats() metotları vardır. Havuzla kullanırken createClientPool() ve BasicPooledClientSideCache kullanılır ve aynı önbellek havuzdaki tüm istemcilerce paylaşılır.
const client = createClient({
RESP: 3,
clientSideCache: {
ttl: 0, // Time-to-live in milliseconds (0 = no expiration)
maxEntries: 0, // Maximum entries to store (0 = unlimited)
evictPolicy: "LRU" // Eviction policy: "LRU" or "FIFO"
}
});Belgelere göre node-redis şu an opt-in/opt-out ve yayın (broadcasting) kiplerini uygulamaz. Sunucunun geçersiz kılma mesajlarını nasıl gönderdiğini client-side caching ve CLIENT TRACKING yazısında anlatıyoruz.
node-redis hata türleri ve desenleri

node-redis hataları reddedilen Promise'ler olarak iletir. Belgelerdeki tablo yaygın hataları şöyle sınıflandırır:
| Hata | Ne zaman oluşur | Önerilen davranış |
|---|---|---|
| ECONNREFUSED | Bağlantı reddedildi | Bekleyerek yeniden dene veya yedeğe dön |
| ETIMEDOUT | Komut zaman aşımı | Bekleyerek yeniden dene |
| ECONNRESET | Bağlantı karşı taraftan sıfırlandı | Bekleyerek yeniden dene |
| EAI_AGAIN | DNS çözümleme hatası | Bekleyerek yeniden dene |
| ReplyError (WRONGTYPE) | Tip uyuşmazlığı | Şemayı veya kodu düzelt |
| ReplyError (BUSY, TRYAGAIN, LOADING) | Redis meşgul veya veri yüklüyor | Sınırlı sayıda bekleyerek yeniden dene |
Ağ hataları err.code alanından, sunucu yanıt hataları ise err.name === 'ReplyError' ve mesajdaki önekten tanınır. Zarif gerileme deseninde yalnızca ağ hatalarında veritabanına dönülür, diğer hatalar yukarı fırlatılır:
try {
const val = await client.get(key);
if (val != null) return val;
} catch (err) {
if (['ECONNREFUSED','ECONNRESET','ETIMEDOUT','EAI_AGAIN'].includes(err.code)) {
logger.warn('Cache unavailable; falling back to DB');
return database.get(key);
}
throw err;
}
return database.get(key);Belgelerdeki yeniden deneme örneği de aynı ayrımı yapar: ETIMEDOUT, ECONNRESET, EAI_AGAIN kodlarında ya da mesajı BUSY, TRYAGAIN veya LOADING içeren ReplyError'da bekleme süresini ikiye katlayarak dener, diğer her durumda hatayı fırlatır. Kendi döngünüzü yazmadan önce, bağlantı kopmalarında node-redis'in zaten kendiliğinden yeniden bağlandığını hatırlayın. Hata kategorilerinin dile bağlı olmayan açıklaması bağlantı ve hata yönetimi yazısında, sunucu mesajlarının anlamı Redis hata mesajları yazısında.
ioredis'ten node-redis'e geçiş
Redis belgeleri ioredis'i, node-redis'ten daha eski olan, hâlâ desteklenen fakat bazı yeni özellik ve performans iyileştirmelerinden yoksun bir kütüphane olarak tanımlar; geçiş rehberi ise ioredis'in node-redis lehine kullanımdan kaldırıldığını (deprecated) belirtir. Yeni projeler için önerilen kütüphane node-redis'tir. Mevcut bir ioredis projesini taşırken değişen başlıca noktalar şunlardır:
| Konu | ioredis | node-redis |
|---|---|---|
| İlk bağlantı | Nesne oluşturulunca kurulur | connect() çağrısı gerekir |
| Olaylar | connect, ready, error, close | connect, ready, error, end, reconnecting |
| Komut adları | Yalnızca küçük harf (hset) | Büyük harf veya camelCase (HSET, hSet) |
| Sonuç alma | Callback ve Promise | Promise; callback için legacy() kipi |
| Serbest komut | call() | sendCommand(), transaction içinde addCommand() |
| Açık pipeline | pipeline() ... exec() | multi() ... execAsPipeline() |
| Tarama | scanStream() | scanIterator() |
| Abonelik | client.on('message', ...) olayı | subscribe() komutu ve ayrı bağlantı |
Argümanların işlenişi de farklıdır. ioredis argümanları redis-cli gibi metne çevirip sırayla gönderir, örneğin client.set('key', 100, 'EX', 10). node-redis ise seçenekleri nesne olarak bekler: client.set('bike:5', 'bike', {EX: 10}). Belgeler ayrıca node-redis'te SETNX metodunun kullanımdan kaldırıldığını ve yerine SET komutunun NX seçeneğinin kullanılması gerektiğini, HMSET yerine çoklu alan alan hSet() kullanılacağını ve ioredis'teki config() yerine configSet(), configGet() gibi ayrı metotlar olduğunu söyler.
// SETNX yerine SET ... NX
await client.set('bike:1', 'bike', {'NX': true});
// Taramada her sayfa bir anahtar dizisi olarak gelir
for await (const keys of client.scanIterator()) {
const values = await client.mGet(keys);
// Process values...
}
// Abonelik için ayrı bağlantı
const subscriber = client.duplicate();
await subscriber.connect();
await subscriber.subscribe('channel', (message) => {
console.log(`Received message: ${message}`);
});scanIterator() her sayfa için bir anahtar koleksiyonu döndürür; bu, sayfadaki anahtarları mGet() gibi çok anahtarlı komutlarla tek seferde okumayı kolaylaştırır. Abonelik açılan bir bağlantı başka komut gönderemediği için client.duplicate() ile aynı ayarlara sahip ayrı bir bağlantı oluşturulur. Abonelik modelinin kendisini Redis Pub/Sub yazısında anlatıyoruz.
Sık yapılan hatalar
- error dinleyicisi eklememek. Tek bir ağ hatası süreci çökertebilir.
- connect() çağırmayı unutmak. ioredis alışkanlığıyla nesneyi oluşturup doğrudan komut göndermek node-redis'te çalışmaz.
- Paylaşılan tek bağlantıda WATCH kullanmak. Eşzamanlı isteklerde izleme durumu karışır;
createClientPool()veexecute()kullanın. - idempotent olmayan komutları çevrimdışı kuyrukla çalıştırmak. Kopma anında yeniden gönderim veriyi bozabilir; gerekirse
disableOfflineQueuekullanın. - Abone bağlantısından normal komut göndermek. Abonelik için
duplicate()ile ayrı bir bağlantı açın.
Sık Sorulan Sorular
node-redis bağlantı koptuğunda kendisi yeniden bağlanır mı?
Evet. Varsayılan strateji rastgele sapmalı üstel geri çekilmedir; socket.reconnectStrategy ile kapatabilir, sabit bekleme verebilir ya da kendi fonksiyonunuzu yazabilirsiniz.
Pipeline için ayrı bir şey yazmam gerekiyor mu?
Çoğu zaman hayır. Aynı olay döngüsü turunda çalışan komutlar kendiliğinden pipeline'a alınır; Promise.all() bunu garanti etmenin kolay yoludur. Açık bir topluluk için multi() ve execAsPipeline() kullanılır.
ioredis kullanmaya devam edebilir miyim?
Belgelerdeki karar ağacına göre mevcut bir ioredis kod tabanınız varsa ve arama, vektör veritabanı veya JSON gibi gelişmiş özelliklere ihtiyacınız yoksa devam edebilirsiniz; bu özellikleri istiyorsanız geçiş rehberiyle node-redis'e taşınmanız önerilir.
Komut başına zaman aşımı nasıl verilir?
AbortController oluşturup sinyalini commandOptions({ signal }) ile komuta verin ve belirlediğiniz süre sonunda abort() çağırın.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Redis öğrenme rehberi · Redis bağlantı URL ve connection pool · MULTI, EXEC ve WATCH · Redis Pub/Sub · client-side caching · Node.js öğrenme rehberi