Back-end/Node.js

[Node.js] 웹 API 서버 만들기 (2)

poppy 2021. 8. 1. 13:20
반응형

https://soohyun6879.tistory.com/176

 

[Node.js] 웹 API 서버 만들기 (1)

저번에 만들었던 SNS 서비스인 NodeBird 앱을 사용하여 웹 API 서버를 만들어보겠습니다. API 는 Application Programming Interface 로 다른 애플리케이션에서 현재 프로그램의 기능을 사용할 수 있게 허용하

soohyun6879.tistory.com

이전 포스팅에서는 웹 API 서버와 토큰 인증하는 부분을 만들었습니다. 이번 포스팅에서는 웹 API 서버에 내가 올린 게시글과 해시태그 검색 결과를 가져오는 부분을 추가하고, 사용량 제한을 구현해보겠습니다.

 

1. 내가 올린 게시글과 해시태그 검색 결과 가져오기

먼저 웹 API 서버 부분을 수정하겠습니다. nodebird-api 프로젝트를 수정하면 됩니다. v1.js 파일을 수정하겠습니다. GET /posts/my 는 내가 올린 게시글을 가져오는 라우터입니다. req.decoded 에는 토큰의 내용물이 저장되어 있습니다. GET /posts/hashtag/:title 은 해시태그 검색 결과를 가져오는 라우터입니다.

// nodebird-api/routes/v1.js
const express = require('express');
const jwt = require('jsonwebtoken');

const { verifyToken } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();
//.. 생략
// 내가 올린 게시글 라우터
router.get('/posts/my', verifyToken, (req, res) => {
  Post.findAll({ where: { userId: req.decoded.id } }) // 토큰의 id로 게시글 조회
    .then((posts) => {
      console.log(posts);
      res.json({ // 게시글 리턴
        code: 200,
        payload: posts,
      });
    })
    .catch((error) => {
      console.error(error);
      return res.status(500).json({
        code: 500,
        message: '서버 에러',
      });
    });
});

// 해시태그 검색 결과 라우터
router.get('/posts/hashtag/:title', verifyToken, async (req, res) => {
  try {
    const hashtag = await Hashtag.findOne({ where: { title: req.params.title } }); // 입력받은 해시태그 확인
    if (!hashtag) {
      return res.status(404).json({
        code: 404,
        message: '검색 결과가 없습니다',
      });
    }
    const posts = await hashtag.getPosts(); // 해시태그가 있다면 해시태그의 게시물 조회
    return res.json({ // 게시물 리턴
      code: 200,
      payload: posts,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '서버 에러',
    });
  }
});

module.exports = router;

 

웹 API를 호출하는 다른 서비스를 수정하겠습니다. nodecat 프로젝트를 수정하면 됩니다. index.js 파일을 수정하겠습니다. 토큰을 발급받는 부분을 반복적으로 사용되므로 따로 함수로 만들어 request 에 저장했습니다.  GET /mypost는 API를 이용해 내가 올린 게시글을 가져오는 라우터입니다. 위에서 API 가 JSON으로 리턴해주므로 JSON으로 값을 가져옵니다. GET /search/:hashtag 는 API를 이용해 해시태그 검색 결과를 가져오는 라우터입니다. 이것도 마찬가지로 JSON으로 값을 가져옵니다.

// nodecat/routes/index.js
const express = require('express');
const axios = require('axios');

const router = express.Router();
const URL = 'http://localhost:3000/v1'; // 요청을 보낼 url

axios.defaults.headers.origin = 'http://localhost:4000'; // origin 헤더 설정
// NodeBird API에 요청을 보내는 함수
const request = async (req, api) => {
  try {
    if (!req.session.jwt) { // 세션에 토큰이 없으면
      const tokenResult = await axios.post(`${URL}/token`, { // 토큰 발급
        clientSecret: process.env.CLIENT_SECRET,
      });
      req.session.jwt = tokenResult.data.token; // 세션에 토큰 저장
    }
    return await axios.get(`${URL}${api}`, { // 토큰을 이용해 API 요청
      headers: { authorization: req.session.jwt },
    });
  } catch (error) {
    if (error.response.status === 419) { // 토큰 만료시 토큰 재발급 받기
      delete req.session.jwt;
      return request(req, api);
    } // 419 외의 다른 에러면
    return error.response;
  }
};

// 내가 올린 게시글 라우터
router.get('/mypost', async (req, res, next) => {
  try {
    const result = await request(req, '/posts/my'); // API를 이용해 올린 게시글 가져오기
    res.json(result.data);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

// 해시태그 검색 결과 라우터
router.get('/search/:hashtag', async (req, res, next) => {
  try {
    const result = await request( // API를 이용해 해시태그 검색 결과 가져오기
      req, `/posts/hashtag/${encodeURIComponent(req.params.hashtag)}`,
    );
    res.json(result.data);
  } catch (error) {
    if (error.code) {
      console.error(error);
      next(error);
    }
  }
});

module.exports = router;

 

NodeBird 와 NodeCat 프로젝트 모두 콘솔에 npm start 를 입력한 후 "http://localhost:4000/mypost" 에 접속하면 다음과 같이 내가 올린 게시글을 볼 수 있습니다.

 

 

2. 사용량 제한 구현하기

인증된 사용자만 API를 사용할 수 있게 해놨지만 과도하게 API를 사용하면 API 서버에 무리가 갑니다. 따라서 일정 기간 내에 API를 사용할 수 있는 횟수를 제한하여 서버에 무리가 가지 않게 하는 것이 좋습니다. 이 기능을 구현할 수 있는 패키지를 먼저 설치하겠습니다.

npm install express-rate-limit

 

먼저 웹 API 서버 부분을 수정하겠습니다. nodebird-api 프로젝트를 수정하면 됩니다. middlewares.js 파일에 미들웨어를 추가하겠습니다. apiLimiter 미들웨어를 라우터에 넣으면 라우터에 사용량 제한이 걸립니다. 사용량 제한을 초과하면 429 상태 코드와 함께 허용량을 초과했다는 메시지를 전송합니다. 현재는 임시로 1분에 한 번 호출 가능하도록 해놨지만 서비스 정책에 맞게 바꾸어야 합니다. deprecated 미들웨어를 사용하면 안되는 라우터에 넣으면 410 코드와 함께 새로운 버전을 사용하라는 메시지가 전송됩니다.

// nodebird-api/routes/middlewares.js
const jwt = require('jsonwebtoken');
const RateLimit = require('express-rate-limit');

//.. 생략
// 사용량 제한 미들웨어
exports.apiLimiter = new RateLimit({
  windowMs: 60 * 1000, // 기준 시간 - 1분
  max: 1, // 허용 횟수
  handler(req, res) { // 제한 초과시 콜백 함수
    res.status(this.statusCode).json({
      code: this.statusCode, // 기본값 429
      message: '1분에 한 번만 요청할 수 있습니다.',
    });
  },
});

// 사용하면 안된다는 경고를 날리는 미들웨어
exports.deprecated = (req, res) => {
  res.status(410).json({
    code: 410,
    message: '새로운 버전이 나왔습니다. 새로운 버전을 사용하세요.',
  });
};

 

사용량 제한이 걸린 새로운 라우터를 만들기 위해 v2.js 파일을 생성합니다. v1.js 파일에서 주석친 부분만 추가하면 됩니다.

// nodebird-api/routes/v2.js
const express = require('express');
const jwt = require('jsonwebtoken');

const { verifyToken, apiLimiter } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();

router.post('/token', apiLimiter, async (req, res) => { // 사용량 제한 미들웨어 추가
  const { clientSecret } = req.body;
  try {
    const domain = await Domain.findOne({
      where: { clientSecret },
      include: {
        model: User,
        attribute: ['nick', 'id'],
      },
    });
    if (!domain) {
      return res.status(401).json({
        code: 401,
        message: '등록되지 않은 도메인입니다. 먼저 도메인을 등록하세요',
      });
    }
    const token = jwt.sign({
      id: domain.User.id,
      nick: domain.User.nick,
    }, process.env.JWT_SECRET, {
      expiresIn: '30m', // 30분
      issuer: 'nodebird',
    });
    return res.json({
      code: 200,
      message: '토큰이 발급되었습니다',
      token,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '서버 에러',
    });
  }
});

router.get('/test', verifyToken, apiLimiter, (req, res) => { // 사용량 제한 미들웨어 추가
  res.json(req.decoded);
});

router.get('/posts/my', apiLimiter, verifyToken, (req, res) => { // 사용량 제한 미들웨어 추가
  Post.findAll({ where: { userId: req.decoded.id } })
    .then((posts) => {
      console.log(posts);
      res.json({
        code: 200,
        payload: posts,
      });
    })
    .catch((error) => {
      console.error(error);
      return res.status(500).json({
        code: 500,
        message: '서버 에러',
      });
    });
});

router.get('/posts/hashtag/:title', verifyToken, apiLimiter, async (req, res) => { // 사용량 제한 미들웨어 추가
  try {
    const hashtag = await Hashtag.findOne({ where: { title: req.params.title } });
    if (!hashtag) {
      return res.status(404).json({
        code: 404,
        message: '검색 결과가 없습니다',
      });
    }
    const posts = await hashtag.getPosts();
    return res.json({
      code: 200,
      payload: posts,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '서버 에러',
    });
  }
});

module.exports = router;

 

새로운 라우터를 생성했으므로 이 라우터를 사용하기 위해 app.js 와 연결합니다. "추가" 라고 주석친 부분만 추가하면 됩니다.

// app.js
// .. 생략
dotenv.config();
const v1 = require('./routes/v1');
const v2 = require('./routes/v2'); // 추가
// .. 생략
app.use('/v1', v1);
app.use('/v2', v2); // 추가
//.. 생략

 

v1.js 파일에 deprecated 미들웨어를 추가하여 v1 으로 접근한 모든 요청에 deprecated 응답을 보내도록 합니다. 

// nodebird-api/routes/v1.js
const express = require('express');
const jwt = require('jsonwebtoken');

const { verifyToken, deprecated } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();

router.use(deprecated);

 

NodeCat 앱에서 새로운 라우터 v2를 호출해보겠습니다. URL 만 수정해주면 됩니다.

 

const express = require('express');
const axios = require('axios');

const router = express.Router();
const URL = 'http://localhost:3000/v2'; // 수정

 

NodeBird 와 NodeCat 프로젝트 모두 콘솔에 npm start 를 입력한 후 "http://localhost:4000/mypost" 에 접속하면 다음과 같이 내가 올린 게시글을 볼 수 있습니다. 다시 접속하면 사용량 초과 화면을 볼 수 있습니다.

 

 

반응형