본문 바로가기
JavaScript/Node.js

Node) API 사용량 제한하기 (express-rate-limit)

by 박채니 2023. 8. 28.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[Node.js 교과서]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

API 사용량 제한하기

- 과도한 API 사용으로 인해 서버에 무리가 갈 수 있음

- 무료 사용자는 1시간에 10번 허용, 유료 사용자는 1시간에 100번 허용 등 API 사용량을 제한

 

패키지 설치

 

$ yarn add express-rate-limit

 

express-rate-limit 패키지를 이용하여 API 사용량을 제한할 수 있습니다.

API 별로 혹은 Router 별로 미들웨어를 만들어 사용합니다.

 

미들웨어 생성

 

middlewares/index.ts

const limiter = rateLimit({
    windowMs: 60 * 10000,
    max: (req: Request, res: Response) => {
        if (req.user?.type === "premium") {
            return 100;
        }
        return 10;
    },
    handler(req: Request, res: Response) {
        res.status(res.statusCode).json({
            code: res.statusCode,
            message: "사용 횟수를 초과했습니다.",
        });
    },
});

// 디도스 공격에는 효과가 없을 수 있음.
// 미들웨어 확장패턴
export const apiLimiter = async (req: Request, res: Response, next: NextFunction) => {
    try {
        let user: User | null = null;
        if (res.locals.decoded?.id) {
            user = await User.findOne({ where: { id: res.locals.decoded.id } });
            if (user) {
                req.user = user;
            }
        }

        limiter(req, res, next);
    } catch (err: any) {
        next(err);
    }
};

User의 type이 premium이라면, 1분에 100번 사용

User의 type이 premium이 아니라면, 1분에 10번 사용할 수 있도록 제한합니다.

 

미들웨어 확장 패턴을 이용하여 사용자의 type를 구한 후 해당 type에 대해서 제한량이 상이한 미들웨어를 생성했습니다.

 

 ⭐️ 사용량이 누적되지 않고 계속 초기화 되어 삽질했던 거 기록..

잘못된 코드

export const apiLimiter = async (req: Request, res: Response, next: NextFunction) => {
    try {
        let user: User | null = null;
        if (res.locals.decoded?.id) {
            user = await User.findOne({ where: { id: res.locals.decoded.id } });
        }

        rateLimit({
            windowMs: 60 * 10000, // ms단위
            max: 1,
            // 요청 횟수 초과 시 실행되는 callback 함수
            handler(_req: Request, _res: Response) {
                _res.status(_res.statusCode).json({
                    code: _res.statusCode,
                    message: "사용 횟수를 초과하였습니다.",
                });
            },
        })(req, res, next);
    } catch (err: any) {
        next(err);
    }
};

rateLimit가 새롭게 호출되어 횟수가 누적되지 않고 초기화되어 설정한 max를 넘어가도 제한이 되지 않았던 것..!

따라서 위처럼 따로 분리하여 초기화 되지 않도록 처리해주었습니다.

(참고한 사이트)

https://www.inflearn.com/questions/919591/10-6-%EC%9E%AC%EC%A7%88%EB%AC%B8

 

10.6 재질문…! - 인프런 | 질문 & 답변

exports.apiLimiter = async (req, res, next) => { let user; if (res.locals.decoded) { user = await User.findOne({ where: { id: res.locals.decoded...

www.inflearn.com

 

 

middlewares/index.ts

/**
 * 버전이 올라가면서 사용하면 안되는 API에 대해서 deprecated 함수를 구현
 * @param req
 * @param res
 */
export const deprecated = (req: Request, res: Response) => {
    res.status(410).json({ code: 410, message: "새로운 버전이 나왔습니다. 새로운 버전을 이용해주세요." });
};

 

 

더이상 사용이 불가한 API에 대한 사용을 막아주는 미들웨어를 생성해주었습니다.

 

미들웨어 적용

 

routes/v1.ts (이전 버전 API)

import express from "express";
import { deprecated, verifyToken } from "../middlewares";
import { createToken, getMyPosts, getPostsByHashtag, tokenTest } from "../controllers/v1";

const router = express.Router();

// 미들웨어를 router마다 사용하지 않고, 공통되기 때문에 아래처럼 사용
router.use(deprecated);

router.post("/token", createToken);
router.get("/test", verifyToken, tokenTest);

router.get("/posts/my", verifyToken, getMyPosts);
router.get("/posts/hashtag/:title", verifyToken, getPostsByHashtag);

export default router;

모든 router에 미들웨어를 하나하나 작성해줄 수 있지만, router.use()를 이용하여 해당 router에 대해서 미들웨어를 한 번에 적용해줄 수도 있습니다.

 

v1 버전 API 요청 시

 

router/v2.ts (최신 버전 - API 사용량 제한)

import express from "express";
import { verifyToken, apiLimiter } from "../middlewares";
import { createToken, getMyPosts, getPostsByHashtag, tokenTest } from "../controllers/v2";

const router = express.Router();

router.post("/token", apiLimiter, createToken);
router.get("/test", verifyToken, apiLimiter, tokenTest);

router.get("/posts/my", verifyToken, apiLimiter, getMyPosts);
router.get("/posts/hashtag/:title", apiLimiter, getPostsByHashtag);

export default router;

모든 router에 대해서 사용량을 제한해주었습니다.

apiLimiter 미들웨어에서 res.locals.decoded를 사용하기 때문에 verifyToken 미들웨어 뒤에 위치해야 하여 이번에는 API별로 미들웨어를 장착시켜주었습니다.

 

10번이 초과되니 위처럼 사용 횟수를 초과했다는 오류 메세지를 리턴 받은 것을 확인할 수 있습니다.