NestJS และ Redis ในปี 2026: Caching, Sessions และคำถามสัมภาษณ์

คู่มือฉบับสมบูรณ์สำหรับการผสาน NestJS กับ Redis สำหรับ caching และการจัดการ session พร้อมคำถามสัมภาษณ์เพื่อเตรียมตัวสำหรับการสัมภาษณ์งาน Node.js

การผสาน NestJS กับ Redis สำหรับ caching และการจัดการ session

การผสาน 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 ซ้ำ

app.module.tstypescript
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

users.service.tstypescript
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> {
    // ตรวจสอบ cache ก่อน
    const cacheKey = `user:${id}`;
    const cached = await this.cache.get<User>(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<User>): Promise<User> {
    const user = await this.usersRepository.update(id, data);
    
    // Invalidate cache หลังจาก update
    await this.cache.del(`user:${id}`);
    
    return user;
  }

  async delete(id: string): Promise<void> {
    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

products.controller.tstypescript
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

main.tstypescript
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 ต่างๆ

session.decorator.tstypescript
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

cache-invalidation.service.tstypescript
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<void> {
    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<void> {
    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
app.module.tstypescript
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 {}

พร้อมที่จะพิชิตการสัมภาษณ์ Node.js / NestJS แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

คำถามสัมภาษณ์: 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 ได้ในแนวนอนในขณะที่รักษาประสิทธิภาพและการจัดการสถานะที่สอดคล้องกัน

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Node.js / NestJS เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 10 กันยายน 2569

แท็ก

#nestjs
#redis
#caching
#nodejs
#interview

แชร์

บทความที่เกี่ยวข้อง