URI란?
웹 페이지의 어떤 메뉴에 접속했을 때 'https://(주소)/movies/3/film?idx=2' 등으로 도메인 주소가 바뀌는 것을 본 적이 있을텐데, 이처럼 자원을 구조와 함께 나타내는 형태를 URI라고 함.
http 메서드 URI 요청 내용
GET/ /category/2/books/ 카테고리 번호가 2인 책의 번호를 조회
POST/ /category/2/books/ 새로 들어온 책의 정보를 등록
PUT/ /category/2/books/4 책 번호가 4인 책의 정보를 변경
DELETE /category/2/books/4 책 번호가 4인 책의 정보를 삭제
<REST API 예시>
우리 서버에 API 서버를 얹어 내가 만든 정보를 다른 사람이 API를 통해 이용할 수 있게 해보자!
API 서버를 따로 만들면, 다른 사람이 내 서버의 정보를 사용할 수 있게 하는 이점 뿐만 아니라, 내 웹의 모바일 서버로 운영할 수도 있음.
API 서버는 웹 사이트의 프론트엔드 부분과 분리되어 운영되기 때문이다.
.
.
이제 직접 API 서버를 만들어보자.
//내 API 서버 만들기
const express = require('express');
const app = express();
app.get('/:type', (req,res) => {
let { type } = req.params;
res.send(type);
});
app.listen(8080);
<결과>
.
.
간단한 게시판 API를 만드는 예제를 살펴보자
//간단한 게시판 API 서버 만들기
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 }));
/* 테스트를 위한 게시글 데이터 */
let boardList = []; //테스트를 위한 게시글 데이터를 boardList라는 전역변수에 리스트 형태로 저장함.(데베에 있는 정보라 생각하기)
let numOfBoard = 0; //numOfBoard 변수는 게시글이 하나씩 추가될 때마다 늘어날 index를 위한 변수임.
/* 라우팅 설정 */
app.get('/', (req, res) => {
res.send('This is api.js');
});
/* 게시글 API */
app.get('/board', (req, res) => { //GET 메서드로 '/board'요청이 들어요면
res.send(boardList); //boardList에 저장된 값을 보여줌
});
app.post('/board', (req, res) => { //POST 메서드로 '/board' 요청이 들어오면 게시글을 등록하는 API가 됨.
const board = { //'board'객체에 req.body로부터 받아온 id, date, title, content 값을 저장(32~38행)
"id": ++numOfBoard,
"user_id": req.body.user_id,
"date": new Date(),
"title": req.body.title,
"content": req.body.content
};
boardList.push(board); //이를 전역변수 boardList에 push해줌.
res.redirect('/board');
});
app.put('/board/:id', (req, res) => { //PUT 메서드로 '/board/:id'요청이 들어오면
//req.params.id 값 찾아 리스트에서 삭제
const findItem = boardList.find((item) => {
return item.id == +req.params.id //:id 값은 req.params.id에 저장됨.
});
const idx = boardList.indexOf(findItem); //boardList 요소 중 id 값이 req.params.id와 같은 요소가 있다면 이를 findItem에 저장
boardList.splice(idx, 1); //해당 요소를 splice() 함수로 제거해줌.(첫번째인자부터 두번째인자까지의 인덱스만 남기고 나머지 요소 없애는 함수임.)
//리스트에 새로운 요소 추가
const board = { //req.params.id를 id로 한 새로운 게시글 데이터를 생성하고 boardList에 넣어줌.
"id": +req.params.id,
"user_id": req.params.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) => { //delete메서드로 '/board/:id'요청이 들오면
//req.params.id 값 찾아 리스트에서 삭제
const findItem = boardList.find((item) => { //:id 값과 동일한 boardList의 요소를 삭제함
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'), '번 포트에서 서버 실행 중..');
});
위 코드를 테스트하려면 curl 명령어를 사용하거나 Postman 같은 API 테스트 자동화 도구를 이용해야 함.
curl는 명령창에서 서버와 통신을 할 수 있게 해주는 명령어 툴
대부분 프로토콜을 지원하므로 url을 가지고 할 수 있는 것을 대부분 할 수있다.
http 프로토콜로 웹 페이지의 소스를 가져온다거나 파일을 다운받을 수도 있다.
cmd에서 curl [request url]을 입력하면됨.(결과 값에서 한글이 깨진다면 chcp 65001 명령어로 명령창 인코딩 형식을 utf-8롤 변경해주면됨.)
1. $ node 경로/board_api.js 명령어로 서버 실행 후
2. 터미널에 $ curl -d '{"user_id":"happy", "title":"안녕하세요.", "content":"API 서버 공부중입니다"}' -H "Content-Type: application/json" -X POST http://localhost:8080/board
3. $ curl localhost:8080/board
Postman은 개발한 API를 테스트하고 테스트 결과를 공유하여 API 개발의 생산성을 높여주는 플랫폼.
<추가>-POST
<변경>-PUT
<삭제>-DELETE
.
.
.
그리고 uuid-apikey라는 것을 생성해 게시글 검색 API를 만들어보자.
npm install uuid-apikey
먼저 uuid-apikey를 생성해보자. 우리가 API를 이용할 때 키를 발급받아 서비스에 접근했던 것처럼, 사용자마다 고유의 키를 발급해주고 등록된 키로 접근하는 사용자에게만 API를 허용하게 해주어야함.
const uuidAPIkey = require('uuid-apikey');
console.log(uuidAPIkey.create());
npm의 uuid-apikey 모듈은 실무에서 적용하기에는 조금 무리가 있는 방법이지만, 보안이 중요하지 않거나 트래픽이 많지 않은 서버의 경우 uuid-apikey 모듈을 이용해 아주 간단한 방법으로 api key 인증을 구현할 수도 있다는 점을 알아두자!
create()함수는 api 키를 하나 발급해주는 함수인데, 이를 통해 하나의 api 키를 생성할 수 있다.
.
.
<게시판에 uuid-apikey 추가하기>
//게시판에 uuid-apikey 추가하기
//게시판에 uuid-apikey 추가하기
const morgan = require('morgan');
const url = require('url'); //요청 주소 뒤에 ?key=value 형식으로 url 쿼리스트링을 보내줄 수 있는데 이런 url을 파싱하기 위해 url모듈불러옴
const uuidAPIkey = require('uuid-apikey');
/* 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 }));
/* 테스트를 위한 API키 */
const key = {
apiKey: 'AEBFX5V-4NQ4991-QNSVTHP-AYWAPGZ',
uuid: '5396fe97-256e-44a4-bd73-bd4657b8ab43'
};
/* 테스트를 위한 게시글 데이터 */
let boardList = [];
let numOfBoard = 0;
/* 라우팅 설정 */
app.get('/', (req, res) => {
res.send('This is api.js');
});
/* 게시글 API */
app.get('/board', (req, res) => { //GET 메서드로 '/board'요청이 들어요면
res.send(boardList); //boardList에 저장된 값을 보여줌
});
app.post('/board', (req, res) => { //POST 메서드로 '/board' 요청이 들어오면 게시글을 등록하는 API가 됨.
const board = { //'board'객체에 req.body로부터 받아온 id, date, title, content 값을 저장(32~38행)
"id": ++numOfBoard,
"user_id": req.body.user_id,
"date": new Date(),
"title": req.body.title,
"content": req.body.content
};
boardList.push(board); //이를 전역변수 boardList에 push해줌.
res.redirect('/board');
});
app.put('/board/:id', (req, res) => { //PUT 메서드로 '/board/:id'요청이 들어오면
//req.params.id 값 찾아 리스트에서 삭제
const findItem = boardList.find((item) => {
return item.id == +req.params.id //:id 값은 req.params.id에 저장됨.
});
const idx = boardList.indexOf(findItem); //boardList 요소 중 id 값이 req.params.id와 같은 요소가 있다면 이를 findItem에 저장
boardList.splice(idx, 1); //해당 요소를 splice() 함수로 제거해줌.(첫번째인자부터 두번째인자까지의 인덱스만 남기고 나머지 요소 없애는 함수임.)
//리스트에 새로운 요소 추가
const board = { //req.params.id를 id로 한 새로운 게시글 데이터를 생성하고 boardList에 넣어줌.
"id": +req.params.id,
"user_id": req.params.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) => { //delete메서드로 '/board/:id'요청이 들오면
//req.params.id 값 찾아 리스트에서 삭제
const findItem = boardList.find((item) => { //:id 값과 동일한 boardList의 요소를 삭제함
return item.id == + req.params.id
});
const idx = boardList.indexOf(findItem);
boardList.splice(idx, 1);
res.redirect('/board');
});
/* 게시글 검색 API using uuid-key */
app.get('/board/:apikey:type', (req, res) => { //:apikey/ 부분에 들어온 값으로 api key 검사.
let{ type, apiKey } = req.params;
const queryData = url.parse(req.url, true).query; //url 모듈의 parse()함수로 요청 쿼리스트링을 queryData, 변수에 넣어줌
if(uuidAPIkey.isAPIKey(apiKey) && uuidAPIkey.check(apikey, key.uuid)){ //isAPIKey()와 .check를 통해 url로 들어온 키가 유효한지 확인
/* .isAPIKey(apikey) - 들어온 apikey가 서버에서 발급한 적이 있는 키인지 확인
check(apikey, key.uuid) 함수는 key와 uuid 짝이 맞는지 확인*/
if(type === 'search'){ //키워드로 게시글 검색 //만약 :type부분에 search/가 들어왔다면 키워드로 게시글 검색
const keyword = queryData.keyword;
const result = boardList.filter((e) => {
return e.title.includes(keyword) //쿼리스트링 keyword의 값이 boardList의 title 값에 포함되어 있는지 includes() 함수로 확인
})
res.send(result); //포함되어 있다면 해당 오브제그를 result에 넣어 응답으로 보내줌.
}
else if(type === 'user'){ //닉네임으로 게시글 검색 //만약 :type부분에 user/가 들어왔다면 닉네임으로 게시글 검색
const user_id = queryData.user_id;
const result = boardList.filter((e) => {
return e.user_id === user_id;
});
res.send(result);
}
else{
res.send('Wrong URL'); //둘 중 하나라도 false라면
}
}else{
res.send('Wrong API Key');
}
});
/* 서버와 포트 연결.. */
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 서버 실행 중..');
});