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" 에 접속하면 다음과 같이 내가 올린 게시글을 볼 수 있습니다. 다시 접속하면 사용량 초과 화면을 볼 수 있습니다.
'Back-end > Node.js' 카테고리의 다른 글
[Node.js] ws 모듈과 Socket.IO 로 웹 소켓 사용하기 (0) | 2021.08.06 |
---|---|
[Node.js] 노드 서비스 테스트 하기 (0) | 2021.08.03 |
[Node.js] 웹 API 서버 만들기 (1) (0) | 2021.07.30 |
[Node.js] 익스프레스로 SNS 서비스 만들기 (3) (0) | 2021.07.27 |
[Node.js] 익스프레스로 SNS 서비스 만들기 (2) (0) | 2021.07.27 |