Node.js REST API 만들기

출처: http://webframeworks.kr/tutorials/nodejs/api-server-by-nodejs-03/#


*사용자 목록조회 API

  • 임시 사용자 데이터
    우린 아직 데이터베이스는 다루지 않음. 대신에 데이터 베이스 역할을 할수 있는 users 변수를 만들어서 사용할 .
    app.js
    파일에 아래와 같은 코드(배열) 추가한다.



  • 라우팅 설정
    클라이언트는 users 변수에 저장된 사용자 데이터를 조회하도록 API 요청할수 있다. API “GET/users”라고 이름을 짓겠다.
    express
    라우팅 함수 get()함수를 이용하여 메소드가 GET임을 설정한뒤 첫번째 파라미터로 경로명인 “/users” 문자열을 넘겨주면 API 대한 라우팅을 만들 있음.


여기까지 작성하면 클라이언트가 “GET/users” 요청할 경우 위에서 설정한 라우팅 함수가 동작할 것이다. 이제 남은것은 라우팅 함수에서 users변수의 값을 클라이언트에 보내주기만 하면 된다. 

라우팅 함수의 파라미터 두번째 파라미터인 res 응답 객채이다. 응답객체의 함수중 json()함수를 사용하면 json형식의 데이터를 클라이언트에게 보내줄 있음. 

  • JSON
    JSON
    일종의 데이터 표현 방법임. 프로그래밍 언어로 오해X
  • 라우팅 로직 작성
    users
    변수에 들어있는 값은 자바스크립트 객체의 배열이다. res.json() 함수는 파라미터로 이값을 받아 JSON형식으로 변환한다. 그리고 요청한 클라이언트로 JSON데이터를 응답해 주는 기능을 한다.


위에 하이라이팅한 부분은 다음과 같이 변경 가능하다.


app.get(‘//users’, (req, res) => res.json(users));

애로우 함수(=>) 함수 본체에 return 구문만 있을 경우 이렇게 한줄로 작성이 가능하다.

  • API 테스트
    curl 명령어를 이용해 테스트 해본다.

    $ curl -X GET ’127.0.0.1:3000/users’ -v



*특정 사용자 조회 API


현제 users id name으로 구성되어 있음.
name
필드는 중복될수 있지만 id 중복되지 않는 실별자이다. (우리가 만들때 그렇게 생각해서 만들었기때문에) 실제 데이터베이스 테이블에 저장될때도 id 유일한 속성인 unique 설정 것이다. 아직 데이터베이스를 다루지 않기 때문에 우리는 id 대해 유일하다고 가정하고 진행할 .

그럼 클라이언트는 특정 사용자를 조회하는 요청을 서버에게 전달할 id 함께 전달한다. id 1 사용자 객체를 조회할 경우 서버는 users 저장된 배열을 뒤져 id 1 객체를 클라이언트로 응답해 주면 되는 것이다. 그럼 이번 API 대한 라우팅 로직을 구현해 볼것이다.

  • 파라미터 설정하는 방법
    클라이언트에서는 id 1 사용자를 조회할 경우 아래 주소로 서버에 요청할 있음.

    GET /users/1

    만약 id 2라면

    GET /users/2

    서버코드를 작성 해보면 아래와 같이 작성할 있다.

    app.get(‘/users/1’, (req, res) => /* … */); // id: 1 대한 요청 처리

    app.get(‘/users/2’, (req, res) => /* … */); // id: 2 대한 요청 처리

    그러나 이는 비효울적인 방법이다.
    다음과 같이 코드를 설정할 .

    app.get(‘/users/:id’, (req, res) => {
    console.log(req.params.id); // 사용자가 입력한 :id 값이 출력됨. (주의 : 서버로 오는 데이터는 전부 문자열 형식이다. 기억할 .)
    });

    $ npm start
    $ curl -X GET ’127.0.0.1/users/1’

    결과를 보면 다음과 같다.


users/1 GET하면
terminal 1 출력이 되고
users/2 GET하면 

terminal 2 출력이 된다.


  • ID 유저 객체 검색
    우선 클라이언트로부터 요청정보를 받는 것에 성공. 이제는 id 기반으로 서버에 있는 users 배열을 뒤져 id 일치하는 데이터를 찾아 요청한 클라이언트로 응답해준다.

    id users 배열을 검색하기 전에 id값에 대한 처리가 남아 있음. 값이 숫자인지를 확인 하는 .
    숫자가 아닌 데이터는 유효하지 않은 데이터이기 때문이다. 예를 들어 “GET /users/alice” 라고 요청할 경우 우리가 설정한 url규칙에 맞지 않음.
    이런경우 다음과 같이 처리하도록 한다.

    app.get(‘/users/:id’, (req, res) => {
    const id = parseInt(req.params.id, 10);
    });

    parseInt() 함수로 id 문자열 값을 정수형으로 변경했다.( users 있는 값은 숫자형 데이터인데 클라이언트가 요청한 데이터는 문자열로 들어오기 때문)
    parseInt() 문자열을 숫자로 변경하는 과정에서 에러가 발생하면 NaN 되돌려주게 되어있음. 이것은 명백히 요청한 클라이언트쪽의 실수라고 있음.
    서버는 이러한 요청에 대해서도 클라이언트에게 적절한 응답을 해줘야함.

    app.get(‘users/:id’, (req, res) => {
    const id = parseInt(req.params.id, 10);
    if(!id){
    return res.status(400).json({err: ‘Incorrect id’});
    }
    });
  • 함수체이닝



  • 클라이언트로 응답
    클라이언트가 요청한 id정보를 검증하였다. 이제 남은 것은 users배열에서 id 일치하는 user 객체를 찾는 일이다.

    코드 작성전에 filter()함수의 사용법에 대해 간단히 짚어넘어 가도록한다.



*
우리의 코드에 원본 작성자가 제시한 filter() 쓰게 되면 다음과 에러가 출력되어서 쓰지 않는다.



  • 404 에러
    만약 filter() 함수로 검색에 실패할 경우 배열( [] ) 반환하게 된다.그리고 배열의 0 인덱스에 접근하게되면 undefined값이 리턴된다.
    결국 user변수에는 undefined값이 저장되는 것이다. 이러한 경우 REST API 규칙에 의해 404 상태코드를 클라이언트에게 알려 줘야 한다. 왜냐하면 id 해당하는 유저 데이터가 없기 때문
    다음의 코드를 추가한다.

    app.get(‘/users/:id’, (req, res) => {
    let user = users.filter(user => user.id === id)[0];
    if(!user){
    return res.status(404).json({err: ‘Unknown user’});
    });
  • 성공 응답


  • 성공 응답 404, 400 출력 결과



*특정 사용자 삭제 API

API 주소는 아래와 같다.

DELETE /users/:id

  • 라우팅 설정
    메소드만 GET에서 DLETE 바뀌었고 뒤에 경로는 조회  API 동일하다

    라우팅을 먼저 설정한다. 그동안 express객체 app get() 함수만 사용했는데 이것은 조회 API 메소드가 GET이었기 때문이다. 삭제 API 메소드인 DELETE 설정하려면 delete() 함수를 사용해야 한다.
    delete()  함수의 파라미터도 get() 함수와 같음. 첫번째 파라미터로 설정할 경로를 문자열로 넘겨줌.

    app.delete(‘/users/:id’, (req, res) => /* … */ );

    파라미터로 받은 id 값에 대한 처리도 해야하는데 이미 “GET /users/:id” API 만들면서 사용했음. 동일한 코드를 사용하면 된다.

    app.delete(‘/users/:id’, (req, res) => {
    const id = parseInt(req.params.id, 10);
    if(!id){
    return res.status(400).json({err: ‘Incorrect id’});
    }
    });
  • 삭제 로직 구현
    잠깐 삭제 API 로직을 생각해보면, 먼저 유저 데이터를 담고 있는 users배열에서 id 해당하는 객체의 위치를 찾아야한다. 그리고 찾은 위치의 요소를 배열에서 제거하면 것이다.
    자바스크립트 배열 메소드중에는 이러한 기능을 잇는 함수가 있는데 다음 두가지 함수를 사용할 것이다.

    findeIndex()
    splice()

  • 배열에서 삭제할 유저 찾기
    findIndex() 배열을 순회하면서 어떤 기준에 맞는 요소의 인덱스를 찾는데 사용한다. users에서 요청한 id 해당하는 객체가 있는 인덱스를 먼저 찾아야함.

    const userIdx = users.findIndex( user => {
    return user.id === id;
    });

    앞에서 보았던 배열 메소드 filter() 유사하게 동작한다. findIndex() 배열의 요소를 순차적으로 돌면서 계산한다. user.findIndex( user, => ) 구문의 경우 처음  user users배열의 요소가 반환되어 나온다. user 대해 id값을 비교한 참을 반환하면 users배열에서의 user 있는 배열 인덱스가 반환된다. 결국 인덱스 정수값은 userIdx 상수에 저장되는 것이다. 만약 id  비교 결과 거짓이 리턴되면 배열의 다음 요소가 user  변수가 할당되어 넘어온다.

    if(userIdx === -1){
    return res.status(404).json({err: ‘Unknown user’});
    }

  • 배열에서 유저 객체 제거
    삭제할 유저 객체의 인덱스를 찾았으면 배열에서 인덱스를 이용해 요소를 삭제해야 한다.
    splice()
    함수를 사용한다.
    첫번째 파라미터로 삭제할 인덱스 숫자를 넘겨주고 두번째 파라미터는 1 넣어준다. 1이라는 값은 첫번째 파라미터를 포함하여 다음 요소 몇개를 삭제하겠냐는 의미이다.
    우리는 1개만 삭제하기 때문에 1 넘겨준다.

    users.splice(userIdx, 1);
  • 응답
    서버쪽에서 데이터를 다루었으니 이제 요청한 클라이언트에게 뭔가 응답해줘야 한다. 사용자를 조회할때는 조회한 사용자 객체를 응답했는데 삭제의 경우는 삭제된 데이터를 보내줄수도 없는 일이다. 보통 두가지 방법이 있다.
    첫번째는
    삭제된 전체 users배열을 다시 응답하는 방법이다. 클라이언트 입장에서는 응답으로 users 배열을 확인하면 요청한 데이터가 삭제되었는지 확인할 있기 때문이다.
    두번째는
    아무 데이터도 보내지 않는 것이다. 여기서 우리는 REST API 상태코드를 사용할 있다. 바로 “No content” 뜻하는 204 상태코드를 응답하는 .

    여기서는 후자의 방법을 사용한다.



처음 get 해을 id: 1 , name: hyun 제대로 출력이 되었고
다음 delete후에 다시 get 404 error 리턴해주는 것을 있다.


*사용자 생성 API

POST /users

  • 라우팅 설정
    express 객체의 함수중 get() delete() 사용해봤다. 이번에는 post()함수를 사용해야한다.

    app.pos(‘/users’, (req, res) => /* … */);

  • 요청 body
    http 요청에 사용되는 데이터는 두가지 방법이 있음

    쿼리 문자열
    body

    쿼리 문자열은 url 포함되어 있는 key/value  값을 의미한다.
    예를 들면 구글에 chirs라는 검색을 했을 주소창에 아래와 비슷한 형태의 주소가 있을 것이다.

    https://www.google.co.kr/#newwindow=1&q=chirs

    https 부분을 프로토콜이라고 한다.
    www.google.co.kr 도메인이라고 한다.
    /#newwindow=1 부분을 경로라고 한다.
    &q=chris 쿼리 문자열 이라고 한다.

    브라우저가 서버로 뭔가 요청할때 q=chirs  라는 형식으로 요청하는 것이다.
    한편 body라는 형식으로 요청데이터를 보낼 있는데 웹브라우저로는 확인하기 어려움. body 데이터는 post 메소드일 경우에만 유효하기 때문
  • body-parser
    express 요청 body 데이터에 접근하기 위해서 doby-parser라는 패키지를 추가해야한다.

    $ npm i body-parser —save

    body-parser 미들웨어이다. 따라서 app.js 있는 express객체에 미들웨어를 추가해서 사용할 있다.
    미들웨어를 추가할 사용하는 express객체의 함수는 use() 함수이다.

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({extended: true}));
  • 파라미터 검증
    비로소 우리 코드에서 요청 body 접근할 있게 되었다. 어떻게 접근하는가?
    -> id 파라미터에 접근하는 방법과 유사하다. 바로 req.body 통해서 body값에 접근할 있다. 클라이언트가 요청시 body name=“hyun”라는 형식으로 요청한다고 생각해보자. 그럼 서버에서는 req.body.name=“hyun”형식으로 데이터가 전송된다.

     app.post(‘/users’, (req, res) => {
    const name = req.body.name || ‘’;
    });

    name 상수에 req.body.name 할당하는 코드를 작성했다. 하지만 만약에 값이 undefined 있는 경우가 있다. 클라이언트가 요청시 name값을 입력하지 않은 경우이다.
    그런경우에는 문자열을 name 상수에 넣도록 하겠다.
    그러나 이는 서비스 관점에서 올바르지 못한방법이다.
    -> 400 Bad Request 응답을 보낼 것이다.

    if(!name.length){
    return res.status(400).json({err: ‘Incorect name’});
    });
  • 새로운 아이디 만들기
    요청한 파라미터 name 제대로 입력되었다면 다음 할일은 새로운 유저 객체를 만드는 . 유저객체는 id name으로 구성되어 있는데, 방금 name 요청 바디를 통해 얻었다.
    남은 것은 id이다. 이것을 얻으려면 기존에 있는 id 중복되지 않은 값을 사용해야 한다. 간단하게 기존에 있는 id 값에서 +1 증가시키면서 추가하기로 한다.
    javascript  배열에서 제공하는 메소드를 사용한다. reduce() 함수

    const id = users.reduce((maxId, user) => {
    return user.id > maxId ? user.id : maxId
    }, 0);

    reduce()  함수는 첫번째 함수를 파라미터로 넘겨주는데 함수는 개의 파라미터를 갖는다.
    첫번째 파라미터로 maxId 우리가 reduce() 함수가 종료될 얻게될 값을 저장하고 있음.
    두번째 파라미터는 배열 users 요소를 순서대로 반환해주는 user객체 이다. 그리고 reduce()함수의 두번째 파라미터로 0 넘겨줬는데 이는 maxId 초기 값이다.

    예를 들어 users.reduce(maxId, user) => {}, 0); 구문이 처음 실행될 maxId 값은 0 되고  user users 배열의 첫번째 요소가 된다.

    코드의 두번째 줄을 살펴보면 어떤 값을 반환하고 있는데 값은 다음 반복문에서 maxId 값이 되는 것이다.
    위의 return하고 있는 삼항연산자는 아래와 같은 코드이다.

    if (user.id > maxId){
    return user.id;
    }else{
    return maxId
    }

    결국 id상수에는 users배열에 있는 id 가장 값이 들어 것이다. 우리는 새로운 id 만들 것이기 때문에 값보다 1 값으로 id상수를 만들 것이다.

    const id = users.reduce((maxId, user) => {
    return user.id > maxId ? user.id : maxId
    }, 0) + 1;
  • 배열에 유저 추가하기
    받은 값을 newUser 상수에 할당한다.

    const newUser = {
    id: id,
    name: name
    };

    그리고 기존의 users배열에 새로운 유저를 추가해준다.

    users.push(newUser);
  • 응답
    마지막으로 서버는 요청한 클라이언트에게 응답을 보내야한다.
    새로만든 유저 데이터를 보내는 것이 합당해 보인다. 그리고 또한가지로 상태코드를 보내는데 “201 Created” 코드를 보내는 것이 REST API형식을 따르는 것이다.

    return res.status(201).json(newUser);

bodyParser 선언


post구현


post get 결과



*Error: listen EADDRINUSE :::3000 에러 해결방법
출처: https://tom7930.tistory.com/27


이런식의 에러가 출력된다면 포트를 이미 사용중이라는 말이다.

아마 노드서버가 정상적으로 종료되지 않았을 것이다.

따라서 노드 서버를 죽일 것이다.

일단 프로세스의 pid를 찾아보자


$ ps -ef | grep app.js



node 서버의 pid 13869이다. 죽이면 된다.


$ kill -9 13869





+ Recent posts