본문 바로가기
Nodejs

[Nodejs] Express에서 redis를 session storage로 사용

by hotdog7778 2023. 10. 15.

Express에서 redis를 session storage로 사용 해서 로그인 / 로그아웃 구현해보기

- 기존에 세션을 메모리에 저장해서 로그인/로그아웃 하던 시스템을 세션을 레디스에 저장하도록 변경!

 

 

1. 로컬에 Redis 설치 및 실행

1-1. Redis Stack 설치

>> stack은 레디스 서버 및 여러가지를 한번에 스택으로 제공한다는 것

>> 레디스 서버는 7버전 임

>> 저는 macOS에서 설치 후 진행

https://redis.io/docs/getting-started/install-stack/mac-os/

 

Install Redis Stack on macOS

How to install Redis Stack on macOS

redis.io

 

1-2. Redis 서버 실행

실행 명령어 : redis-stack-server

>> 백그라운드에서 실행 : nohup redis-stack-server &

 

종료 명령어 : 

>> 프로세스 확인 : ps aux | grep redis

>> 프로세스 종료 : kill [PID]

 

※ Redis-Stack에 포함된 Redis 서버 UI인 RedisInsight를 실행 (초보자라 UI 환경 사용)

실행 명령어 : redisinsight

 

※ Redis-Stack으로 설치 후 Redis 설정을 하고싶다면

https://yonguri.tistory.com/148 참고

 

 

2. 프로젝트에 npm 패키지 설치

설치 명령어 : npm i connect-redis redis

 

✓ connect-redis: 

Express에서 redis를 session storage로 제공하는 라이브러리 (CommonJS(require) 및 ESM(import) 모듈을 모두 지원)

https://www.npmjs.com/package/connect-redis

 

✓ redis:

Express에서 redis client를 생성할 수 있는 라이브러리 (레디스서버에 연결하는데 필요)

https://www.npmjs.com/package/redis?activeTab=readme

 

✓ 구현할때 사용한 버전들

이번에는 CommonJS 및 레디스7 버전을 사용

{

    "express": "^4.18.2",
    "express-session": "^1.17.3",

    "redis": "^4.6.10",

    "connect-redis": "^7.1.0",

}

 

 

3. 코드에 적용 해보기

 - redis 패키지로 redisClient 생성 및 redis서버에 connect

 - '생성한 redisClient' + 'connect-redis 패키지' 를 사용해서 redisStore 객체 생성

 - 생성한 redisStore를 session의 저장소로 설정

 

const dotenv = require('dotenv');
dotenv.config();
const { PORT, REDIS_HOST, REDIS_PORT, SESSION_KEY } = process.env;

const express = require('express');
const session = require('express-session');
const app = express();

// redis 버전 7에서 commonJS 형식
const redis = require('redis');
const RedisStore = require('connect-redis').default;

// main APP
async function main() {
  const redisClient = await redis
    .createClient({
      // 목적지 레디스 서버에 대한 정보를 적지 않으면 기본값으로 localhost 6379포트로 연결
      url: `redis://@${REDIS_HOST}:${REDIS_PORT}`,

      // 그외 목적지 레디스 서버 정보는
      // format : redis[s]://[[username][:password]@][host][:port][/db-number]
      // createClient({
      // url: 'redis://alice:foobared@awesome.redis.server:6380'
      // });
    })
    .on('connect', () => console.log('Initiating a connection to the server	'))
    .on('ready', () => {
      console.log('Client is ready to use');
    })
    .on('end', () => {
      console.log('Connection has been closed');
    })
    .on('error', (err) => console.log('Redis Client Error', err))
    .on('reconnecting', () => {
      console.log('Client is trying to reconnect to the server	');
    })
    .connect();

  //// redis에서 키, 밸류 가져오기
  // const value = await redisClient.get('key');

  //// redis에 키, 밸류 저장하기
  // await redisClient.set('key', 'value');

  // await redisClient.set('key', 'value', {
  //   EX: 10,
  //   NX: true
  //});

  //// 트랜잭션(Multi/Exec)
  // .multi()를 호출하여 트랜잭션을 시작한 다음 명령을 연결합니다.
  // 완료되면 .exec()를 호출하면 결과가 포함된 배열이 반환됩니다.
  // 그니까 set 과 get을 함께한다. -> set 과 get의 결과를 배열로 한번에 받는다
  // await client.set('another-key', 'another-value');
  // const [setKeyReply, otherKeyValue] = await client
  //   .multi()
  //   .set('key', 'value')
  //   .get('another-key')
  //   .exec(); // ['OK', 'another-value']

  //// 연결 끊기 quit()
  // 서버에 QUIT 명령을 전송하여 Redis에 대한 클라이언트 연결을 정상적으로 종료합니다.
  // 종료하기 전에 클라이언트는 대기열에 남아 있는 모든 명령을 실행하고 각 명령에 대해 Redis로부터 응답을 받습니다.

  // 비동기 처리
  // const [ping, get, quit] = await Promise.all([
  //   client.ping(),
  //   client.get('key'),
  //   client.quit()
  // ]); // ['PONG', null, 'OK']

  // 비동기 처리 및 에러시 처리까지
  // try {
  //   await client.get('key');
  // } catch (err) {
  //   // ClosedClient Error
  // }

  //// 연결 끊기 disconnect()
  // await redisClient.disconnect();

  let redisStore = new RedisStore({
    client: redisClient,
    // prefix: 'myapp:',
  });

  app.use(
    session({
      secret: SESSION_KEY,
      resave: false, // 필수옵션 : 경량 세션을 강제로 활성화
      saveUninitialized: false, // 권장사항: 데이터가 존재할 때만 세션을 저장
      cookie: {
        httpOnly: true,
        maxAge: 60 * 60000, // 1h
      },
      store: redisStore,
    })
  );

  app.set('view engine', 'ejs');
  app.use('/views', express.static(__dirname + '/views'));
  app.use('/static', express.static(__dirname + '/static'));
  app.use(express.urlencoded({ extended: true }));
  app.use(express.json());

  // 레디스 연결을 확인 및 재연결 시도하는 미들웨어
  app.use((req, res, next) => {
    // if (!redisClient.connected) {
    if (!redisClient.connect) {
      redisClient.connect();
    }
    next();
  });

  const router = require('./routes/user');
  app.use('/', router);

  // TODO: 404 에러 처리
  app.get('*', (req, res) => {
    res.render('404');
  });

  app.listen(PORT, () => {
    console.log(`http://localhost:${PORT}`);
  });
}

main();

 

 

4. 로그인 / 로그아웃 테스트 후 Redis서버에 유저정보인 세션 데이터가 저장되는지 확인

 

 - 로그인 시도 

로그인 된 상태

 


 - 로그인 후 redis 서버에 저장된 데이터

 - 1시간으로 설정한 세션 유효시간과 로그인한 유저의 아이디가 레디스에 저장된것을 확인

RedisInsight 로 들여다본 redis 서버

 

 

- 끝 -

'Nodejs' 카테고리의 다른 글

[Nodejs] Express JWT 토큰 발급/검증  (0) 2023.10.19
[nodejs] Express-session  (0) 2023.10.16
[Nodejs] 싱글 스레드  (1) 2023.10.04
[Nodejs] 동기/비동기, 블로킹/논블로킹  (0) 2023.10.02
[Nodejs] Event-driven (이벤트기반)  (0) 2023.09.30