NestJS i Redis w 2026: Cachowanie, Sesje i Pytania Rekrutacyjne
Kompleksowy przewodnik po integracji Redis z NestJS obejmujący cache-manager, zarządzanie sesjami i strategie invalidacji.

Integracja Redis z NestJS znacząco podnosi wydajność aplikacji, zastępując zapytania do bazy danych szybkimi odczytami z pamięci. Kombinacja @nestjs/cache-manager do cachowania i express-session z connect-redis do zarządzania sesjami obejmuje dwa najpopularniejsze przypadki użycia Redis w aplikacjach backendowych.
Cachowanie Redis w NestJS redukuje obciążenie bazy danych poprzez przechowywanie często używanych danych w pamięci. Zarządzanie sesjami z Redis umożliwia skalowanie horyzontalne dzięki współdzieleniu stanu sesji między wieloma instancjami.
Konfiguracja @nestjs/cache-manager z Redis
Moduł cache-manager został wydzielony do osobnego pakietu w NestJS 10. Instalacja wymaga zarówno wrappera NestJS, jak i odpowiedniego adaptera cache. Adapter @keyv/redis jest zalecanym podejściem do integracji Redis z cache-manager v5.
# Install required packages
npm install @nestjs/cache-manager cache-manager @keyv/redisRejestracja modułu konfiguruje połączenie z Redis oraz domyślny TTL. Opcja isGlobal udostępnia cache we wszystkich modułach bez konieczności ponownego importowania.
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import KeyvRedis from '@keyv/redis';
@Module({
imports: [
CacheModule.registerAsync({
isGlobal: true,
useFactory: () => ({
stores: [
new KeyvRedis(process.env.REDIS_URL || 'redis://localhost:6379'),
],
ttl: 60000, // Default TTL in milliseconds
}),
}),
],
})
export class AppModule {}Tablica stores przyjmuje wiele magazynów cache dla wielopoziomowego cachowania. Środowiska produkcyjne zazwyczaj używają Redis jako głównego magazynu z opcjonalnym fallbackiem do pamięci lokalnej dla środowisk deweloperskich.
Wstrzykiwanie Cache i Cachowanie na Poziomie Serwisu
Token CACHE_MANAGER zapewnia bezpośredni dostęp do cache dla operacji na poziomie serwisu. To podejście oferuje większą kontrolę niż automatyczne cachowanie HTTP, gdy logika biznesowa wymaga specyficznej invalidacji cache.
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
import { UsersRepository } from './users.repository';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@Inject(CACHE_MANAGER) private cache: Cache,
private usersRepository: UsersRepository,
) {}
async findById(id: string): Promise<User | null> {
// Check cache first
const cacheKey = `user:${id}`;
const cached = await this.cache.get<User>(cacheKey);
if (cached) {
return cached;
}
// Cache miss: fetch from database
const user = await this.usersRepository.findById(id);
if (user) {
await this.cache.set(cacheKey, user, 300000); // 5 minutes TTL
}
return user;
}
async update(id: string, data: Partial<User>): Promise<User> {
const user = await this.usersRepository.update(id, data);
// Invalidate cache on update
await this.cache.del(`user:${id}`);
return user;
}
}Wzorzec klucza cache user:{id} umożliwia precyzyjną invalidację. Bardziej złożone wzorce jak user:* wymagają poleceń specyficznych dla Redis przez klienta bazowego.
CacheInterceptor do Automatycznego Cachowania Odpowiedzi HTTP
Wbudowany CacheInterceptor automatycznie cachuje odpowiedzi HTTP GET. Zastosowanie na poziomie kontrolera cachuje wszystkie endpointy GET, podczas gdy zastosowanie na poziomie metody zapewnia granularną kontrolę.
import { Controller, Get, UseInterceptors, Param } from '@nestjs/common';
import { CacheInterceptor, CacheTTL, CacheKey } from '@nestjs/cache-manager';
import { ProductsService } from './products.service';
@Controller('products')
@UseInterceptors(CacheInterceptor)
export class ProductsController {
constructor(private productsService: ProductsService) {}
@Get()
@CacheTTL(120000) // Override default TTL: 2 minutes
async findAll() {
return this.productsService.findAll();
}
@Get(':id')
@CacheKey('product-detail') // Custom cache key prefix
async findOne(@Param('id') id: string) {
return this.productsService.findById(id);
}
}Interceptor domyślnie generuje klucze cache z URL żądania. Niestandardowe dekoratory @CacheKey() nadpisują to zachowanie dla tras, gdzie parametry query nie powinny wpływać na cachowanie. Koncepcje interceptorów i guards w NestJS często pojawiają się na rozmowach technicznych.
Domyślny klucz cache używa pełnego URL włącznie z parametrami query. Dwa żądania do /products?page=1 i /products?page=2 tworzą osobne wpisy w cache. Użyj @CacheKey() gdy paginacja nie powinna tworzyć zduplikowanych odpowiedzi w cache.
Zarządzanie Sesjami z Redis i express-session
NestJS domyślnie działa na Express, co czyni express-session standardowym wyborem do zarządzania sesjami. Adapter connect-redis przechowuje dane sesji w Redis zamiast w pamięci, umożliwiając trwałość sesji podczas restartów serwera.
# Install session packages
npm install express-session connect-redis redis
npm install -D @types/express-sessionKonfiguracja sesji odbywa się w main.ts przed uruchomieniem aplikacji. Klient Redis łączy się niezależnie od konfiguracji cache-manager.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
import { createClient } from 'redis';
import RedisStore from 'connect-redis';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Initialize Redis client
const redisClient = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379',
});
await redisClient.connect();
// Configure session middleware
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET || 'change-this-in-production',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
}),
);
await app.listen(3000);
}
bootstrap();Opcja saveUninitialized: false zapobiega zapisywaniu pustych sesji, redukując zużycie pamięci Redis. Ustawienie secure: true dla ciasteczek wymaga HTTPS w środowisku produkcyjnym.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Dostęp do Sesji w Kontrolerach i Guardach
Dane sesji są dostępne przez obiekt request. Typowany interfejs sesji poprawia doświadczenie programisty i wychwytuje błędy podczas kompilacji.
export interface SessionData {
userId?: string;
email?: string;
roles?: string[];
loginAt?: Date;
}
declare module 'express-session' {
interface SessionData {
userId?: string;
email?: string;
roles?: string[];
loginAt?: Date;
}
}Rozszerzony typ sesji udostępnia właściwości sesji z autouzupełnianiem.
import { Controller, Post, Body, Req, HttpCode } from '@nestjs/common';
import { Request } from 'express';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
@HttpCode(200)
async login(@Body() loginDto: LoginDto, @Req() req: Request) {
const user = await this.authService.validateUser(
loginDto.email,
loginDto.password,
);
// Store user data in session
req.session.userId = user.id;
req.session.email = user.email;
req.session.roles = user.roles;
req.session.loginAt = new Date();
return { message: 'Logged in successfully' };
}
@Post('logout')
@HttpCode(200)
async logout(@Req() req: Request) {
return new Promise((resolve, reject) => {
req.session.destroy((err) => {
if (err) reject(err);
resolve({ message: 'Logged out successfully' });
});
});
}
}Guard chroni trasy sprawdzając ważność sesji. Ten wzorzec integruje się płynnie z systemem autoryzacji i RBAC NestJS.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class SessionGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
return !!request.session?.userId;
}
}Strategie Invalidacji Cache w Aplikacjach NestJS
Invalidacja cache to trudniejszy problem w cachowaniu. Trzy strategie mają zastosowanie w różnych scenariuszach:
Wygaśnięcie czasowe (TTL) sprawdza się dla danych, które zmieniają się przewidywalnie lub gdy niewielka nieaktualność jest akceptowalna. Katalogi produktów, wartości konfiguracyjne i treści publiczne pasują do tego wzorca.
Invalidacja zdarzeniowa czyści wpisy cache gdy dane bazowe się zmieniają. Wymaga to jawnych wywołań cache.del() w operacjach aktualizacji i usuwania.
Invalidacja wzorcowa usuwa wiele powiązanych kluczy. Redis wspiera usuwanie wzorcowe przez polecenia SCAN i DEL, ale cache-manager abstrahuje to.
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
@Injectable()
export class CacheInvalidationService {
constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}
// Single key invalidation
async invalidateUser(userId: string): Promise<void> {
await this.cache.del(`user:${userId}`);
}
// Multiple related keys
async invalidateUserRelated(userId: string): Promise<void> {
const keys = [
`user:${userId}`,
`user:${userId}:profile`,
`user:${userId}:preferences`,
];
await Promise.all(keys.map((key) => this.cache.del(key)));
}
// Clear entire cache (use sparingly)
async clearAll(): Promise<void> {
await this.cache.reset();
}
}Metoda reset() czyści wszystkie dane w cache, przydatna podczas wdrożeń lub migracji danych, ale niebezpieczna w produkcji jeśli wywołana przypadkowo.
Pooling Połączeń Redis i Wsparcie dla Klastra
Wdrożenia produkcyjne wymagają poolingu połączeń do efektywnej obsługi równoległych żądań. Biblioteka ioredis zapewnia wsparcie dla klastra i pooling połączeń od razu.
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis, { Cluster } from 'ioredis';
@Injectable()
export class RedisConfig {
constructor(private configService: ConfigService) {}
createClient(): Redis | Cluster {
const clusterNodes = this.configService.get<string>('REDIS_CLUSTER_NODES');
if (clusterNodes) {
// Cluster mode
const nodes = clusterNodes.split(',').map((node) => {
const [host, port] = node.split(':');
return { host, port: parseInt(port, 10) };
});
return new Redis.Cluster(nodes, {
redisOptions: {
password: this.configService.get('REDIS_PASSWORD'),
},
});
}
// Standalone mode
return new Redis({
host: this.configService.get('REDIS_HOST', 'localhost'),
port: this.configService.get('REDIS_PORT', 6379),
password: this.configService.get('REDIS_PASSWORD'),
maxRetriesPerRequest: 3,
});
}
}Dla aplikacji używających TypeORM lub Prisma, połączenia z bazą danych i cache powinny być konfigurowane osobno z własnymi pulami połączeń.
Pytania Rekrutacyjne o NestJS i Redis
Rozmowy techniczne dotyczące cachowania w Node.js obejmują zarówno zrozumienie koncepcyjne, jak i praktyczną implementację. Oto najczęściej spotykane pytania:
Dlaczego używać Redis zamiast cachowania w pamięci?
Cachowanie w pamięci nie persystuje między restartami i nie może być współdzielone między wieloma instancjami. Redis zapewnia persystencję, replikację i współdzielony stan dla aplikacji skalowanych horyzontalnie. Kompromisem jest opóźnienie sieciowe w porównaniu z dostępem do pamięci lokalnej.
Czym różni się cache-manager od bezpośredniego użycia Redis?
Biblioteka cache-manager zapewnia ujednolicone API dla różnych magazynów cache. Przejście z pamięci do Redis wymaga zmiany konfiguracji magazynu bez modyfikacji kodu serwisu. Bezpośrednie użycie Redis oferuje więcej funkcji specyficznych dla Redis jak pub/sub, sorted sets i skrypty Lua.
Czym jest cache stampede i jak temu zapobiegać?
Cache stampede występuje gdy popularny wpis cache wygasa i wiele równoległych żądań jednocześnie trafia do bazy danych. Strategie zapobiegania obejmują:
- Odświeżanie z blokadą: Jedno żądanie odświeża cache podczas gdy pozostałe czekają
- Probabilistyczne wczesne wygasanie: Losowe odświeżanie przed wygaśnięciem TTL
- Odświeżanie w tle: Oddzielny proces odświeża wygasające wpisy
Kiedy używać CacheInterceptor vs. cachowanie na poziomie serwisu?
CacheInterceptor pasuje do bezstanowych endpointów GET gdzie odpowiedź zależy tylko od URL. Cachowanie na poziomie serwisu obsługuje scenariusze gdzie invalidacja cache musi nastąpić przy zmianach danych lub gdy operacje inne niż GET wymagają cachowania.
Jak obsługiwać ataki session fixation z sesjami Redis?
Należy regenerować ID sesji po uwierzytelnieniu. Wywołanie req.session.regenerate() przed zapisaniem danych użytkownika zapobiega wykorzystaniu przez atakujących ID sesji z przed uwierzytelnienia.
// Secure login with session regeneration
async login(@Body() loginDto: LoginDto, @Req() req: Request) {
const user = await this.authService.validateUser(
loginDto.email,
loginDto.password,
);
return new Promise((resolve, reject) => {
req.session.regenerate((err) => {
if (err) reject(err);
req.session.userId = user.id;
req.session.email = user.email;
resolve({ message: 'Logged in successfully' });
});
});
}Seniorzy wyjaśniają kompromisy między strategiami cachowania. Junior może powiedzieć "cachowanie poprawia wydajność". Senior wyjaśnia kiedy cachowanie dodaje złożoność bez korzyści, np. dla wysoce spersonalizowanych lub szybko zmieniających się danych.
Monitoring Wydajności i Metryki Redis
Aplikacje produkcyjne potrzebują wglądu w wydajność cache. Kluczowe metryki obejmują:
- Hit rate: Procent żądań obsłużonych z cache. Poniżej 80% sugeruje problemy z TTL lub strategią kluczy.
- Zużycie pamięci: Polecenie Redis
INFO memorypokazuje bieżące i szczytowe zużycie pamięci. - Liczba połączeń: Zbyt wiele połączeń wskazuje na brakujący pooling.
- Liczba wyrzuceń: Niezerowa liczba wyrzuceń oznacza pełny cache usuwający wpisy.
import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisHealthService {
constructor(private redis: Redis) {}
async getMetrics() {
const info = await this.redis.info('stats');
const memory = await this.redis.info('memory');
const stats = this.parseInfo(info);
const mem = this.parseInfo(memory);
const hits = parseInt(stats.keyspace_hits, 10);
const misses = parseInt(stats.keyspace_misses, 10);
const hitRate = hits / (hits + misses) || 0;
return {
hitRate: (hitRate * 100).toFixed(2) + '%',
usedMemory: mem.used_memory_human,
connectedClients: stats.connected_clients,
evictedKeys: stats.evicted_keys,
};
}
private parseInfo(info: string): Record<string, string> {
return info.split('\n').reduce((acc, line) => {
const [key, value] = line.split(':');
if (key && value) acc[key.trim()] = value.trim();
return acc;
}, {} as Record<string, string>);
}
}Warto udostępnić te metryki przez endpoint health check lub zintegrować z Prometheus do monitoringu produkcyjnego.
Cachowanie Redis i Sesje we Wdrożeniach Produkcyjnych NestJS
Wdrażanie NestJS z Redis wymaga uwagi na kilka kwestii operacyjnych:
- Zarządzanie connection stringiem: Przechowywanie URL Redis w zmiennych środowiskowych, nie w kodzie. Używanie secrets management dla haseł.
- Graceful shutdown: Zamykanie połączeń Redis podczas zamykania aplikacji aby zapobiec wyciekom połączeń.
- Logika ponawiania: Konfiguracja exponential backoff dla błędów połączenia z Redis.
- Oddzielne instancje Redis: Rozważenie oddzielnych instancji Redis dla cache i sesji. Eviction cache nie powinno wpływać na dane sesji.
import { Injectable, OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisCleanup implements OnModuleDestroy {
constructor(private redis: Redis) {}
async onModuleDestroy() {
await this.redis.quit();
}
}Dla architektur mikroserwisowych Redis służy również jako broker wiadomości przez wbudowaną warstwę transportową NestJS, łącząc cachowanie, sesje i komunikację między serwisami.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Znajdziesz błąd w Node.js / NestJS?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 10 września 2026
Udostępnij
Powiązane artykuły

Szybka Lista JavaScript w 2026: Windowing, Wirtualizacja i Optymalizacja Wydajności
Poznaj techniki windowing i wirtualizacji do renderowania tysięcy elementów w JavaScript. TanStack Virtual, react-virtuoso i najlepsze praktyki dla wydajnych list w 2026 roku.

Wydajna lista JavaScript ListView w 2026: Techniki wirtualizacji i optymalizacji
Opanuj wirtualizację list JavaScript z TanStack Virtual, react-virtuoso i react-window. Renderuj ponad 100 tysięcy elementów płynnie dzięki technikom windowing.

NestJS i MongoDB w 2026: Mongoose, Agregacje i Pytania Rekrutacyjne
Opanuj NestJS z MongoDB i Mongoose 9. Poznaj wzorce projektowania schematów, potoki agregacji i przygotuj się do rozmów kwalifikacyjnych z praktycznymi przykładami.