Skip to main content

Command Palette

Search for a command to run...

[TIL] HW5 Refactoring - 1

04/26/23

Updated
[TIL] HW5 Refactoring - 1

1. How to Store Refresh Token in Redis

Related Post: How to store refresh token in Redis

I tried to store the refresh token in the Redis cloud.

login API before refactoring

const redisClient = redis.createClient({
  url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
  legacyMode: true,
});
redisClient.on("connect", () => {
  console.info("Redis connected!");
});
redisClient.on("error", (err) => {
  console.error("Redis Client Error", err);
});
redisClient.connect().then();
const redisCli = redisClient.v4;
await redisCli.set(refreshToken, userId);

res.cookie("accessToken", `Bearer ${accessToken}`);
res.cookie("refreshToken", `Bearer ${refreshToken}`);
  • I tried implementing a login page where accessToken and refreshToken are reissued in the following manner:

    1. First, I created a redisClient and established a connection.

    2. I used the set(key, value) method to store the refreshToken in Redis as the key and the userId as the value.

    3. I stored the access and refresh tokens in the cookie.

auth-middleware before refactoring

const redisClient = redis.createClient({
  url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
  legacyMode: true,
});
redisClient.on("connect", () => {
  console.info("Redis connected!");
});
redisClient.on("error", (err) => {
  console.error("Redis Client Error", err);
});
redisClient.connect().then(); 
const redisCli = redisClient.v4;

if (!isRefreshTokenValid) {
  await redisCli.del(authRefreshToken);
  return res
    .status(419)
    .json({ message: "Refresh Token has been expired." });
}
if (!isAccessTokenValid) {
  const accessTokenId = await redisCli.get(authRefreshToken);
  if (!accessTokenId)
    return res.status(419).json({
      message: "Refresh Token does not exist in the server.",
    });
    newAccessToken = createAccessToken(accessTokenId);
    res.cookie("accessToken", `Bearer ${newAccessToken}`);
    return res.json({ message: "Access Token is newly generated. Please try again." });
}
const { userId } = getAccessTokenPayload(authAccessToken);

const user = await Users.findOne({ where: { userId } });
res.locals.user = user

next();
  • In the auth-middleware, we check if the access and refresh tokens are valid, and if the access token has expired, we regenerate it. Here are the steps we take:

    1. First, we create a redisClient and connect it to Redis.

    2. We perform token validation. If the refreshToken has already expired, we remove its key-value pair from Redis. We retrieve the userId information from Redis using the refreshToken, and if the access token has expired, we regenerate it.

    3. We extract the userId payload from the access token and store it in res.locals.user.

    4. We pass the request to the next middleware.


When creating the Redis connection, the code was duplicated. Therefore, refactoring was done by declaring a class defining functions related to Redis.

Refactoring by creating RedisClient Class

class RedisClient {
    constructor() {
        this.redisClient = redis.createClient({
            url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
            legacyMode: true,
        });

        this.redisConnected = false;
    }

    async initialize() {
        this.redisClient.on("connect", () => {
            this.redisConnected = true;
            console.info("Redis connected!");
        });
        this.redisClient.on("error", (err) => {
            console.error("Redis Client Error", err);
        });
        if (!this.redisConnected) this.redisClient.connect().then(); 
    }

    setRefreshToken = async (refreshToken, userId) => {
        await this.initialize();
        await this.redisClient.set(refreshToken, userId);
    };

    getRefreshToken = async (refreshToken) => {
        await this.initialize();
        const token = await this.redisClient.get(refreshToken);
        return token;
    };

    deleteRefreshToken = async (refreshToken) => {
        await this.initialize();
        await this.redisClient.del(refreshToken);
    };
}
  • An error occurred when logging in again after logging in once, which was throw new Error('Socket already opened'); Error: Socket already opened error. This was caused by trying to reopen the Redis connection even though it was already connected. To fix this error, a boolean variable named this.redisConnected was created in the constructor, and Redis connection was only attempted when this value was false.

    In the login API and auth-middleware, an instance was created using redisClient = new RedisClient(); and existing Redis methods were replaced with functions defined in the class.

Use 3-layered Architecture

  1. Repository Layer (actual layer accessing Redis): The RedisClientclass is put into the users repository, and two classes are exported using objects. module.exports = { UserRepository, RedisClientRepository };

  2. Service Layer: RedisClientRepository is imported and related functions are defined. If data processing is required, it is done in this layer.

  3. Controller Layer: RedisClientService is imported, and an instance is created within the UserController class. redisClient = new RedisClientService(); The Redis methods are used in this layer through the instance.

  1. Body data validation using joi module

Before Refactoring

Validation using if statements

if (typeof nickname !== "string")
            throw new Error("412/Nickname format is incorrect.");

if (password !== confirmedPassword)
  throw new Error("412/Passwords are not matching.");

if (password.length < 4 || typeof password !== "string")
  throw new Error("412/Password format is incorrect.");

if (password.includes(nickname.toLowerCase()))
  throw new Error("412/Password includes nickname.");

const nickNameRegex = new RegExp("^[a-zA-z0-9]{3,}$", "g");
if (!nickNameRegex.test(nickname))
  throw new Error("412/Nickname format is incorrect.");

After refactoring: joi

const { nickname, password } = await signupSchema
            .validateAsync(req.body)
            .catch((error) => {
                console.error(error);
                throw new Error(`412/${error}`);
            });
  • joi.js file
signupSchema: Joi.object({
  nickname: Joi.string()
  .regex(/^[a-zA-Z0-9]{3,}$/)
  .messages({
    "string.base": "Nickname format is incorrect.",
    "string.pattern.base": "Nickname format is incorrect.",
    "string.empty": "Nickname format is incorrect.",
    "string.min": "Nickname format is incorrect.",
  }),
  password: Joi.string().min(4).required().messages({
    "string.base": "Password format is incorrect.",
    "string.empty": "Password format is incorrect.",
    "string.min": "Password format is incorrect.",
  }),
  confirmedPassword: Joi.string().valid(Joi.ref("password")).required().messages({
    "string.base": "Passwords are not matching.",
    "any.only": "Passwords are not matching.",
    "string.empty": "Passwords are not matching.",
  }),
}),s