API: Application Programming Interface
인터페이스는 서로 다른 두 가지 사이에 소통을 할 수 있게 해주는 접점을 의미합니다.
여기서 서로 다른 두 가지는 사람과 사람, 장치와 사람, 시스템과 장치 등이 될 수 있습니다.
즉 API는 응용프로그램 사이의 소통 방법입니다.
Open API: request
node서버가 클라이언트가 되어 서버에 요청을 보내는 경우 http 통신이 필요합니다.
http 통신은 클라이언트의 요청(request)이 있을 때만 서버가 응답(response)을 보내 요청한 데이터를 전송하고 곧 바로 연결을 종료하는 방식입니다.
다른 서비스의 API요청을 위해서는 request, axios 등의 http 통신 라이브러리가 필요합니다.
Open API: axios
request 모듈로 http통신을 하는 코드는 비동기적으로 작업이 이루어집니다.
때문에 비동기 처리를 따로 해주어야 하는데, 이런 이유로 request 모듈보다는 axios 모듈을 사용합니다.
axios는 Promise를 반환하고, async/await 까지 사용할 수 있습니다.
axios 는 구형 브라우저를 지원하고, 요청을 중단시킬 수도 있고, 응답 시간 초과를 설정하는 방법도 있습니다.
또한 CSRF 보호 기능이 내장되어 있으며 JSON 형식으로 자동 변환이 가능합니다.
*CSRF(Cross Site Request Forgery): 웹의 취악점 중 하나로 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹에 요청하게 만드는 공격입니다.
캐싱 구현하기
Redis: Remote Dictionary Service
모든 데이터를 메모리에 저장하고 조회하기 때문에 데이터를 읽고 쓰는 것이 관계형 데이터베이스 보다 빠릅니다.
Redis는 특히 리스트, 배열 같은 데이터를 처리하는데 유용합니다.
보통 쿠키와 세션을 Redis에 저장합니다.
const redis = require('redis');
const client = redis.createClient(6379, '127.0.0.1');
/* redis version 3.1.2 */
/* npm install redis@^3.1.2 */
client.get('myKey', (err, value) => {
console.log(value);
});
Redis 모듈도 마찬가지고 require를 통해 불러오고 client 변수에 redis 서버를 담습니다.
createClient 인자는 Redis 서버를 연결할 포트와 도메인이고, 기본값은 6379 포트와 내 local server입니다.
해당 값을 변경하고 싶다면 redis.conf 파일에서 변경하면 됩니다.
Redis client 값을 얻어오기 위해 get 함수를 사용하면 됩니다.
첫번째 인자는 얻고 싶은 값의 키이고, 두번째 인자는 콜백 함수인데, 이 콜백 함수의 두번째 인자에 내가 원하는 값이 들어오게 됩니다.
/* redis version 3.1.2 */
/* npm install redis@^3.1.2 */
const path = require('path');
const dotenv = require('dotenv');
dotenv.config({ path: path.resolve(__dirname, "../../.env") }); const morgan = require('morgan');
const axios = require('axios');
/* express app generate */
const express = require('express');
const app = express();
/* redis connect */
const redis = require('redis');
const client = redis.createClient(6379, '127.0.0.1');
client.on('error', (err) => {
console.log('Redis Error : ' + err);
});
/* 포트 설정 */
app.set('port', process.env.PORT);
/* 공통 미들웨어 */
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
/* 라우팅 설정 */
app.get('/airkorea', async (req, res) => {
// Redis의 Irange()함수를 통해airItems라는 키 값이 Redis에 저장되어 있으면,
// 해당 데이터를 캐시에서 불러와 res.send()를 통해 전송하고,
// 만약 캐시된 데이터가 없다면 API요청을 통해 원하는 정보를 얻어와 airItems를 생성하고 ,
// redis의 repush() 함수를 통해 데이터를 저장합니다.
// Irange()의 첫번째 인자는 찾을 데이터의 키값(airItem), 두번째 , 세번 째 인자는 가져올 배열의 인덱스 입니다.
// 0~-1까지라고 하면 배열의 전체 인덱스를 말합니다.
// 마지막 인자는 콜백 함수로 오류가 발생하면 err, 반환을 찾는 데이터가 있으면 그 데이터를 cacheItems에 반환합니다.
await client.lrange('airItems', 0, -1, async (err, cachedItems) => {
if (err) throw err;
if (cachedItems.length) { // data in cache
res.send(` 데이터가 캐시에 있습니다.
관측 지역: ${cachedItems[0]} / 관측 시간: ${cachedItems[1]}
미세먼지 ${cachedItems[2]} 초미세먼지 ${cachedItems[3]} 입니다.`);
} else { // data not in cache
const serviceKey = process.env.airServiceKey;
const airUrl = "<http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?">;
let parmas = encodeURI('serviceKey') + '=' + serviceKey;
parmas += '&' + encodeURI('numOfRows') + '=' + encodeURI('1');
parmas += '&' + encodeURI('pageNo') + '=' + encodeURI('1');
parmas += '&' + encodeURI('dataTerm') + '=' + encodeURI('DAILY');
parmas += '&' + encodeURI('ver') + '=' + encodeURI('1.3');
parmas += '&' + encodeURI('stationName') + '=' + encodeURI('마포구');
parmas += '&' + encodeURI('returnType') + '=' + encodeURI('json')
const url = airUrl + parmas;
try {
const result = await axios.get(url);
const airItem = {
"location": result.data.ArpltnInforInqireSvcVo["stationName"], // 지역
"time": result.data.list[0]['dataTime'], // 시간대
"pm10": result.data.list[0]['pm10Value'], // pm10 수치
"pm25": result.data.list[0]['pm25Value'] // pm25 수치
}
const badAir = [];
// pm10은 미세먼지 수치
if (airItem.pm10 <= 30) {
badAir.push("좋음😀");
} else if (pm10 > 30 && pm10 <= 80) {
badAir.push("보통😐");
} else {
badAir.push("나쁨😡");
}
//pm25는 초미세먼지 수치
if (airItem.pm25 <= 15) {
badAir.push("좋음😀");
} else if (pm25 > 15 && pm10 <= 35) {
badAir.push("보통😐");
} else {
badAir.push("나쁨😡");
}
const airItems = [airItem.location, airItem.time, badAir[0], badAir[1]];
airItems.forEach((val) => {
client.rpush('airItems', val); // redis에 저장
});
// Redis의 expire()함수를 통해 데이터의 유효 시간을 정해줍니다.
client.expire('airItems', 60 * 60);
res.send(`캐시된 데이터가 없습니다.`);
} catch (error) {
console.log(error);
}
}
})
});
/* 서버와 포트 연결.. */
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 서버 실행 중 ..')
});
서버 직접 만들기
REST API
요청된 주소만 보고도 어떤 내용에 관한 요청인지 예상할 수 있게 하는 형식을 REST라고 합니다.
http 요청 메서드
POST: 데이터를 추가합니다(CREATE)
GET: 데이터를 읽거나 조회합니다(READ)
PUT: 데이터를 변경합니다(UPDATE)
PATCH: (PUT이 데이터를 통째로 변경한다면) 데이터를 일부만 바꿉니다.
DELETE: 데이터를 삭제합니다.
const morgan = require('morgan');
/* express app generate */
const express = require('express');
const app = express();
/* 포트 설정 */
app.set('port', process.env.PORT || 8080);
/* 공통 미들웨어 */
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
/* 테스트를 위한 게시글 데이터 */
// 테스트를 위한 게시글 데이터를 boardList라는 전역변수에 리스트의 형태로 저장합니다.
// numOfBoard 변수는 게시글이 하나씩 추가될 때마다 늘어날 index를 위한 변수입니다.
let boardList = [];
let numOfBoard = 0;
/* 라우팅 설정 */
app.get('/', (req, res) => {
res.send('This is api.js');
});
/* 게시글 API */
// GET 메서드로 '/board' 요청이 들어오면 boardList에 저장된 값을 보여줍니다.
app.get('/board', (req, res) => {
res.send(boardList);
});
// POST 메서드로 '/board' 요청이 들어오면 게시글을 등록하는 API가 됩니다.
// 'board' 객체에 req.body로부터 받아온 id, date, title, content 값을 저장합니다.
// 전역변수 boardList에 push 합니다.
app.post('/board', (req, res) => {
const board = {
"id": ++numOfBoard,
"user_id": req.body.user_id,
"date": new Date(),
"title": req.body.title,
"content": req.body.content
};
boardList.push(board);
res.redirect('/board');
});
app.put('/board/:id', (req, res) => {
// req.params.id 값 찾아 리스트에서 삭제
// PUT 메서드로 '/board/:id' 요청이 들어오면 :id 값은 req.params.id에 저장됩니다.
const findItem = boardList.find((item) => {
return item.id == +req.params.id
});
const idx = boardList.indexOf(findItem);
// boardList 요소 중 id 값이 req.params.id와 같은 요소가 있다면 이를 findItem에 저장하고 해당 요소를 splice()함수로 제거합니다.
// splice()는 첫번째 인자로부터 두번째 인자까지의 인덱스만 남기고 나머지 요소를 없애는 함수입니다.
// req.params.id를 id로 한 새로운 게스글 데이터를 생성하고 boardList 에 넣어줍니다.
boardList.splice(idx, 1);
// 리스트에 새로운 요소 추가
const board = {
"id": +req.params.id,
"user_id": req.body.user_id,
"date": new Date(),
"title": req.body.title,
"content": req.body.content
};
boardList.push(board);
res.redirect('/board');
});
app.delete('/board/:id', (req, res) => {
// req.params.id 값 찾아 리스트에서 삭제
// DELETE 메서드로 '/board/:id'라는 요청이 들어오면 :id 값과 동일한 boardList의 요소를 삭제합니다.
const findItem = boardList.find((item) => {
return item.id == +req.params.id
});
const idx = boardList.indexOf(findItem);
boardList.splice(idx, 1);
res.redirect('/board');
});
/* 서버와 포트 연결.. */
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 서버 실행 중 ..')
});
'프레임워크 > Express' 카테고리의 다른 글
[2차 정리]템플릿 엔진 (0) | 2023.09.07 |
---|---|
[2차 정리]node.js 웹소켓 (0) | 2023.09.07 |
[2차 정리]node.js로 서버 제작시 필요한 상식 (0) | 2023.09.07 |
[1차 정리]node.js 프로젝트 제작 시 각 파일의 역할 (0) | 2023.09.07 |
[1차 정리]프로젝트 시작 전 기본 상식(서버 제작, rest, cookie, https, cluster) (0) | 2023.09.06 |