API 서버 직접 만드는 방법

함배·2023년 2월 2일
0

REST API란?

  • 클라이언트에서 서버에게 요청을 보낼 때 API를 REST 형식으로 만들어 주는 경우가 아직까지는 대세이다.
  • 요청된 주소만 보고도 어떤 내용에 관한 요청인지를 예상할 수 있게 하는 형식을 REST라고 한다.
  • '/movies'라는 요청의 주소만으로 '영화의 리스트를 보내달라는 요청이구나'를 알 수 있게 라우팅을 짜는 것이 기본이 된다.
  • RESTful하게 API를 만들면 'URI'와 'http 요청 메서드(GET, POST, DELETE, PUT(PATCH))'를 보고도 '아~ 이게 게시물을 올려달라는 요청이구나. 회원 정보를 가져오라는 요청이구나'를 파악할 수 있다.

    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 서버를 만들어보자.

//내 API 서버 만들기

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

app.get('/:type', (req,res) => {
    let { type } = req.params;
    res.send(type);
});

app.listen(8080);

<결과>

  • '/:type'처럼, URI의 콜론(:) 뒤에 오는 path는 어떤 것이든 올 수 있다!(4행)
  • 그렇게 들어온 변수는 req.params에 저장되는 라우트 파라미터이다.(5행)
  • 만약 URL 요청이 '/예시테스트중'이라는 주소가 들어왔으면 req.params.type에는 "예시테스트중"이라는 문자열이 저장된다.(5행)

.
.
간단한 게시판 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 명령어 이용하기--> 오류...왜...?

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 이용하기

Postman은 개발한 API를 테스트하고 테스트 결과를 공유하여 API 개발의 생산성을 높여주는 플랫폼.
<추가>-POST

<변경>-PUT

<삭제>-DELETE

.
.
.

uuid-apikey 이용하기

그리고 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'), '번 포트에서 서버 실행 중..');
});
  • 키워드로 게시글을 검색하는 로직은 /api/board/search?keyword=날씨, 이런 식으로 키워드에 대한 게시글을 결과로 보내주는 응답을 처리하는 로직을 가지고 있음.
    .
    .
    .
    .
profile
아둥바둥 개발자

0개의 댓글