# NestJS และ Redis ในปี 2026: Caching, Sessions และคำถามสัมภาษณ์ > คู่มือฉบับสมบูรณ์สำหรับการผสาน NestJS กับ Redis สำหรับ caching และการจัดการ session พร้อมคำถามสัมภาษณ์เพื่อเตรียมตัวสำหรับการสัมภาษณ์งาน Node.js - Published: 2026-09-10 - Updated: 2026-09-10 - Author: Anthony Fillion-Maillet - Tags: nestjs, redis, caching, nodejs, interview - Reading time: 12 min --- การผสาน NestJS กับ Redis เปลี่ยนแปลงประสิทธิภาพของแอปพลิเคชันอย่างมากโดยการแทนที่การเรียก database ด้วยการค้นหาในหน่วยความจำ การผสมผสานระหว่าง @nestjs/cache-manager สำหรับ caching และ express-session กับ connect-redis สำหรับการเก็บรักษา session ครอบคลุมกรณีการใช้งาน Redis ที่พบบ่อยที่สุดสองกรณีในแอปพลิเคชัน backend > **ประเด็นสำคัญ** > > Redis caching ใน NestJS ลดภาระ database โดยการเก็บข้อมูลที่เข้าถึงบ่อยไว้ในหน่วยความจำ การจัดการ session ด้วย Redis ช่วยให้สามารถ horizontal scaling ได้โดยการแชร์สถานะ session ระหว่าง instance ต่างๆ ## การตั้งค่า @nestjs/cache-manager กับ Redis โมดูล cache-manager ถูกย้ายไปยัง package แยกต่างหากตั้งแต่ NestJS 10 การติดตั้งต้องการทั้ง wrapper ของ NestJS และ cache store พื้นฐาน Adapter @keyv/redis เป็นวิธีที่แนะนำสำหรับการผสาน Redis กับ cache-manager v5 ```bash # ติดตั้ง package ที่จำเป็น npm install @nestjs/cache-manager cache-manager @keyv/redis ``` การลงทะเบียนโมดูลกำหนดค่าการเชื่อมต่อ Redis และ TTL เริ่มต้น ตัวเลือก `isGlobal` ทำให้ cache พร้อมใช้งานในทุกโมดูลโดยไม่ต้อง import ซ้ำ ```typescript // app.module.ts 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, // TTL เริ่มต้นในหน่วยมิลลิวินาที }), }), ], }) export class AppModule {} ``` อาร์เรย์ `stores` รับ cache store หลายตัวสำหรับ multi-tier caching สภาพแวดล้อม production มักใช้ Redis เป็น store หลัก พร้อมกับ fallback in-memory ที่เป็นทางเลือกสำหรับ development ## Cache Injection และ Service-Level Caching Token `CACHE_MANAGER` ให้การเข้าถึง cache โดยตรงสำหรับการดำเนินการระดับ service วิธีนี้ให้การควบคุมมากกว่า HTTP caching อัตโนมัติเมื่อ business logic กำหนดว่าเมื่อใดควร invalidate cache ```typescript // users.service.ts 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 { // ตรวจสอบ cache ก่อน const cacheKey = `user:${id}`; const cached = await this.cache.get(cacheKey); if (cached) { return cached; } // Cache miss: ดึงข้อมูลจาก database const user = await this.usersRepository.findById(id); if (user) { await this.cache.set(cacheKey, user, 300000); // TTL 5 นาที } return user; } async update(id: string, data: Partial): Promise { const user = await this.usersRepository.update(id, data); // Invalidate cache หลังจาก update await this.cache.del(`user:${id}`); return user; } async delete(id: string): Promise { await this.usersRepository.delete(id); await this.cache.del(`user:${id}`); } } ``` รูปแบบ cache-aside ที่ implement ด้านบนจะตรวจสอบ cache ก่อนการ query database การ invalidation เกิดขึ้นในการดำเนินการ write เพื่อรักษาความสอดคล้องของข้อมูลระหว่าง cache และแหล่งข้อมูลหลัก ## HTTP Response Caching ด้วย Interceptor NestJS มี `CacheInterceptor` สำหรับการ cache response HTTP โดยอัตโนมัติ Decorator `@CacheTTL()` และ `@CacheKey()` ปรับแต่งพฤติกรรมต่อ endpoint ```typescript // products.controller.ts import { Controller, Get, Param, UseInterceptors } 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) // 2 นาทีสำหรับรายการสินค้า async findAll() { return this.productsService.findAll(); } @Get(':id') @CacheTTL(300000) // 5 นาทีสำหรับสินค้าแต่ละรายการ async findOne(@Param('id') id: string) { return this.productsService.findById(id); } @Get('category/:category') @CacheKey('products-by-category') @CacheTTL(180000) async findByCategory(@Param('category') category: string) { return this.productsService.findByCategory(category); } } ``` Interceptor จะ cache response GET โดยอัตโนมัติโดยใช้ URL ของ request เป็น key เริ่มต้น Custom cache key มีประโยชน์เมื่อหลาย route ต้องแชร์หรือมี cache entry ที่แตกต่างกัน ## การจัดการ Session ด้วย Redis Store HTTP session ที่สนับสนุนโดย Redis อนุญาตให้ข้อมูล session คงอยู่ข้ามการรีสตาร์ทเซิร์ฟเวอร์และการ horizontal scaling Package express-session ผสานกับ NestJS ผ่าน middleware ```bash npm install express-session connect-redis ioredis npm install -D @types/express-session ``` การกำหนดค่า session store ต้องการ instance ของ Redis client Package ioredis ให้การสนับสนุน Redis ที่ดีกว่าพร้อม clustering และ sentinel ```typescript // main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as session from 'express-session'; import RedisStore from 'connect-redis'; import { Redis } from 'ioredis'; async function bootstrap() { const app = await NestFactory.create(AppModule); const redisClient = new Redis( process.env.REDIS_URL || 'redis://localhost:6379' ); app.use( session({ store: new RedisStore({ client: redisClient }), secret: process.env.SESSION_SECRET || 'your-secret-key', resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // 24 ชั่วโมง sameSite: 'lax', }, }), ); await app.listen(3000); } bootstrap(); ``` ตัวเลือก `resave: false` ป้องกันการบันทึก session ซ้ำหากไม่มีการแก้ไข การตั้งค่า `saveUninitialized: false` รับประกันว่า session ว่างเปล่าจะไม่เติมเต็มพื้นที่เก็บข้อมูล Redis ## การเข้าถึง Session Data ใน Controller ข้อมูล session พร้อมใช้งานผ่าน request object Custom decorator ทำให้การเข้าถึง session ง่ายขึ้นข้าม handler ต่างๆ ```typescript // session.decorator.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const GetSession = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return data ? request.session?.[data] : request.session; }, ); // auth.controller.ts import { Controller, Post, Body, Get } from '@nestjs/common'; import { GetSession } from './session.decorator'; import { Session } from 'express-session'; interface UserSession extends Session { userId?: string; role?: string; loginAt?: Date; } @Controller('auth') export class AuthController { @Post('login') async login( @Body() credentials: LoginDto, @GetSession() session: UserSession, ) { // ตรวจสอบ credentials const user = await this.authService.validate(credentials); // เก็บข้อมูล user ใน session session.userId = user.id; session.role = user.role; session.loginAt = new Date(); return { message: 'เข้าสู่ระบบสำเร็จ' }; } @Post('logout') async logout(@GetSession() session: UserSession) { return new Promise((resolve, reject) => { session.destroy((err) => { if (err) reject(err); resolve({ message: 'ออกจากระบบสำเร็จ' }); }); }); } @Get('profile') async getProfile(@GetSession('userId') userId: string) { if (!userId) { throw new UnauthorizedException('Session ไม่ถูกต้อง'); } return this.usersService.findById(userId); } } ``` ## กลยุทธ์การ Invalidate Cache กลยุทธ์การ invalidate cache ที่เหมาะสมรักษาความสอดคล้องของข้อมูล รูปแบบทั่วไป ได้แก่ time-based expiration, event-driven invalidation และ manual purging ```typescript // cache-invalidation.service.ts import { Injectable, Inject } from '@nestjs/common'; import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; import { OnEvent } from '@nestjs/event-emitter'; @Injectable() export class CacheInvalidationService { constructor(@Inject(CACHE_MANAGER) private cache: Cache) {} // Event-driven invalidation @OnEvent('user.updated') async handleUserUpdate(payload: { userId: string }) { await this.cache.del(`user:${payload.userId}`); await this.cache.del('users:list'); } @OnEvent('product.created') async handleProductCreate() { // Invalidate cache สินค้าทั้งหมดเมื่อสร้างสินค้าใหม่ await this.invalidatePattern('products:*'); } // Pattern-based invalidation ต้องการการเข้าถึง Redis โดยตรง async invalidatePattern(pattern: string): Promise { const store = this.cache.stores[0] as any; const redis = store?.opts?.redis || store?.redis; if (redis) { const keys = await redis.keys(pattern); if (keys.length > 0) { await redis.del(...keys); } } } // Manual cache warming async warmCache(): Promise { const popularProducts = await this.productsService.findPopular(); for (const product of popularProducts) { await this.cache.set(`product:${product.id}`, product, 600000); } } } ``` ## Rate Limiting ด้วย Redis Redis ให้พื้นที่เก็บข้อมูลแบบกระจายสำหรับ rate limit counter Package @nestjs/throttler รองรับ Redis เป็น storage backend ```bash npm install @nestjs/throttler @nestjs/throttler-storage-redis ioredis ``` ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { ThrottlerModule } from '@nestjs/throttler'; import { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis'; import { Redis } from 'ioredis'; @Module({ imports: [ ThrottlerModule.forRootAsync({ useFactory: () => ({ throttlers: [ { name: 'short', ttl: 1000, limit: 3, }, { name: 'medium', ttl: 10000, limit: 20, }, { name: 'long', ttl: 60000, limit: 100, }, ], storage: new ThrottlerStorageRedisService( new Redis(process.env.REDIS_URL || 'redis://localhost:6379') ), }), }), ], }) export class AppModule {} ``` ## คำถามสัมภาษณ์: Caching และ Redis ใน NestJS ต่อไปนี้เป็นคำถามสัมภาษณ์ทั่วไปที่ทดสอบความรู้เกี่ยวกับการผสาน Redis ในแอปพลิเคชัน NestJS **คำถาม 1: ความแตกต่างระหว่าง cache-aside และ read-through caching คืออะไร?** Cache-aside กำหนดให้โค้ดของแอปพลิเคชันตรวจสอบ cache อย่างชัดเจนและเติมข้อมูลเมื่อเกิด cache miss Read-through caching มอบหมายกระบวนการนี้ให้กับ cache layer เอง NestJS cache-manager ใช้รูปแบบ cache-aside ที่ developer รับผิดชอบต่อ logic ของ get/set **คำถาม 2: จะจัดการกับ cache stampede ได้อย่างไร?** Cache stampede เกิดขึ้นเมื่อหลาย request พร้อมกันพบ cache miss และทั้งหมด query database วิธีแก้ไขรวมถึง request coalescing (การจัดคิว request ที่ซ้ำกัน), probabilistic early expiration, หรือ lock-based population ที่มีเพียง request เดียวที่เติม cache ในขณะที่ request อื่นรอ **คำถาม 3: ทำไมต้องใช้ Redis สำหรับ session แทนที่ session ที่ใช้หน่วยความจำ?** Session ที่ใช้หน่วยความจำไม่คงอยู่หลังจากรีสตาร์ทเซิร์ฟเวอร์และไม่สามารถแชร์ข้าม instance ได้ Redis ให้ความคงทน, horizontal scaling และคุณสมบัติ expiration ในตัวสำหรับการจัดการ session **คำถาม 4: อธิบายความแตกต่างระหว่าง cache TTL และ sliding expiration?** TTL แบบคงที่จะหมดอายุ entry ในช่วงเวลาที่กำหนดตั้งแต่สร้าง Sliding expiration จะรีเซ็ตตัวจับเวลาทุกครั้งที่มีการเข้าถึงข้อมูล Redis ดั้งเดิมรองรับเฉพาะ TTL แบบคงที่; sliding expiration ต้องการ logic ของแอปพลิเคชันเพื่ออัปเดตเวลา expiration เมื่ออ่าน **คำถาม 5: จะ cache ข้อมูลที่แตกต่างกันต่อ user ได้อย่างไร?** ใช้ cache key ที่รวม user identifier: `user:${userId}:preferences` สิ่งนี้รับประกันการแยกระหว่างข้อมูลของ user ที่แตกต่างกันในขณะที่ยังได้รับประโยชน์จาก caching **คำถาม 6: การแลกเปลี่ยนระหว่าง cache hit rate และ memory usage คืออะไร?** TTL ที่สูงกว่าเพิ่ม hit rate แต่ใช้หน่วยความจำมากขึ้นและมีความเสี่ยงที่ข้อมูลจะเก่า TTL ที่สั้นกว่ารักษาข้อมูลให้ใหม่แต่เพิ่มภาระ database วิธีแก้ไขที่ดีที่สุดสร้างสมดุลทั้งสองปัจจัยตามรูปแบบการเข้าถึงและความอ่อนไหวของข้อมูล ## สรุป การผสาน Redis กับ NestJS ต้องการความเข้าใจเกี่ยวกับรูปแบบ caching, กลยุทธ์การจัดการ session และการพิจารณาการกำหนดค่า โมดูล @nestjs/cache-manager ให้ abstraction ที่สะอาดสำหรับการดำเนินการ caching ในขณะที่ express-session กับ connect-redis จัดการการเก็บรักษา session การผสมผสานทั้งสองช่วยให้แอปพลิเคชัน NestJS scale ได้ในแนวนอนในขณะที่รักษาประสิทธิภาพและการจัดการสถานะที่สอดคล้องกัน --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/node-nestjs/nestjs-redis-caching-sessions-interview-questions