프로젝트/할일목록(TODO) 앱

할일목록(TODO) 앱 2 - 기본적인 서버 설정 및 Mongo DB 연동

syleemomo 2021. 10. 5. 13:30
728x90

 

* 비주얼 스튜디오 에디터 열기

프로젝트 root에서 비주얼 스튜디오 에디터 열기

 

* 코드 테스트하기

console.log("hello world !")

index.js 파일에 해당 코드를 작성하고 ctrl + F5 를 하면 코드가 실행된다. 또는 server 폴더에서 node index.js 라고 명령어를 입력하면 실행된다. 

 

* express 서버 설치하기

server 폴더 내부 package.json 이 위치한 곳에서 설치를 진행해야 한다. 

Node modules 폴더 생성 확인

node_modules 라는 서버 관련 패키지가 설치된걸 확인할 수 있다.

express 패키지 설치 확인

package.json 파일에 express 서버 관련 패키지가 등록된 걸 확인할 수 있다.

 

* express 서버 구동하기

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()

app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

index.js 파일에 위와 같이 코드를 작성한다.

터미널에서 위와 같이 입력하면 서버가 구동된다. 이때 반드시 package.json 이 위치한 곳에서 서버를 시작해야 한다.

 

* 포트가 이미 사용중인 경우이거나 서버가 비정상적으로 종료된 경우 에러 처리

리눅스 - sudo fuser -k 5000/tcp (자신의 서버 포트번호 사용)

윈도우

참조문서

 

Error: listen EADDRINUSE: address already in use 3000;

I have a .env file in my root directory with PORT = 3000; inside In my app.js, I am using the .env file to listen to port 3000 require('dotenv').config(); const express = require('express'); const...

stackoverflow.com

 

* nodemon 설치 

node로 서버를 구동하면 코드가 변경되었을때 서버를 수동으로 재시작해야 변경된 사항이 반영된다. nodemon 패키지를 설치하면 서버가 구동중인 경우에도 코드를 수정했을때 자동으로 재시작하고 변경 사항이 반영된다.

nodemon 실행시 에러가 발생하는 경우 해결방법

nodemon 에러해결

 

VS CODE supervisor, nodemon 오류 (ps1 파일을 로드할 수 없습니다)

nodejs 공부하면서 1클릭 1오류 경험중이다.역시 셋팅하는게 가장 어려운거지... 하면서 자기위로중https://singa-korean.tistory.com/21이분 글을 보고 바로 해결했다. 발생 이유는 스크립트 실행 권한이 제

velog.io

이제 index.js 파일에서 포트나 코드를 변경하면 자동으로 재시작한다.

 

* 서버 응답 테스트 

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()

app.get('/hello', (req, res) => { // URL 응답 테스트
    res.send('hello world !')
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

index.js 파일에 위와 같이 코드를 추가한다. 브라우저에서 localhost:5000/hello URL 주소로 접속하면 화면에 "hello world" 라는 글자가 표시된다. 작성한 URL 응답 테스트 코드를 미들웨어라고 한다. 미들웨어 중에서도 애플리케이션 미들웨어에 포함된다. '/hello' 는 라우트 경로이다. 뒤따라오는 콜백함수는 라우트 핸들러라고 한다. 사용자가 브라우저에서 특정 URL 주소를 입력하면 서버에서는 해당 URL 주소가 정의된 미들웨어를 찾는다. 그런 다음 뒤따라오는 라우트 핸들러 함수를 실행한다. res.send() 를 실행하면 서버는 더이상 나머지 코드를 실행하지 않고 응답을 브라우저로 전송한다. 

서버 응답 테스트

 

크롬 개발자 도구(F12)를 열어서 네트워크(Network) 탭을 확인해보자. hello 라는 파일을 클릭하고 Headers 탭을 확인해보면 사용자가 서버에 접속할때 브라우저와 서버 간에 서로 메세지 정보를 주고 받는다는 것을 확인할 수 있다. 브라우저에서 서버로 전송되는 정보를 요청(request)이라고 하고, 서버에서 브라우저로 전송되는 정보를 응답(response)라고 한다. 

응답(response) 메세지

응답(response)에는 메타데이터에 해당하는 응답 헤더(response headers)와 서버에서 생성한 실제 데이터인 응답 본문(response body)으로 나뉜다. 응답본문은 다른 말로 페이로드(payload)라고도 한다. 응답헤더에는 다양한 속성이 존재한다. Access-Control-Allow-Origin, Connection, ETag, keep-Alive 등이다. 브라우저는 이러한 속성값을 이용하여 서버가 보낸 응답을 그에 맞게 처리한다. 

 

요청(request) 메세지

 

요청(request)에는 메타데이터에 해당하는 요청 헤더(request headers)와 사용자가 생성한 실제 데이터인 요청 본문(request body)으로 다시 나뉜다. 요청본문은 다른 말로 페이로드(payload)라고도 한다. 요청헤더에는 다양한 속성이 존재한다. Accept, Cache-Control, Connection, Host, User-Agent 등이다. 서버는 이러한 속성값을 이용하여 브라우저가 보낸 요청을 그에 맞게 처리한다. 

 

* 서버 오류 처리

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()

app.get('/hello', (req, res) => { // URL 응답 테스트
    res.send('hello world !')
})

app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

index.js 파일에 위와 같이 에러처리 코드를 추가한다. 현재 정의된 URL은 /hello 뿐이다. 그 외에 어떠한 URL을 요청해도 해당하는 응답이 없기 때문에 이러한 경우 에러처리가 필요하다. 흔히 404 페이지를 보여준다. 

반드시 URL 주소들이 정의된 미들웨어보다 하단에 위치해야 한다. 그렇지 않으면 사용자가 접속하고 싶은 URL 주소(/hello)에 접근하면 "Sorry can't find page" 라는 에러처리 미들웨어만 실행된다. 에러처리코드보다 아래쪽에 위치한 라우트 핸들러 함수는 실행되지 않는다. 왜냐하면 에러처리 코드는 모든 요청에 대하여 공통적으로 실행되고, 실행이 끝나면 res.send() 를 이용하여 브라우저로 응답을 보내기 때문이다. 그래서 나머지 서버코드는 실행되지 않는다. 즉, 서버코드는 작성순서가 매우 중요하다. 

404 오류

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()

app.get('/hello', (req, res) => { // URL 응답 테스트
    res.send('hello world !')
})


// 폴백 핸들러 (fallback handler)
app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack)
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

서버 내부에서 오류가 발생한 경우 이를 처리해줄 로직이 필요하다. 주로 브라우저에서 응답없음 (500)이나 internal server error 으로 표시된다.  사용자가 요청한 URL 이 없거나 서버 자체 오류가 발생한 경우 이를 처리하는 로직을 폴백 핸들러(fallback handler)라고 한다. 폴백 핸들러는 반드시 내가 접속하고자 하는 URL 주소보다 아래쪽에 위치해야 한다. 

 

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()

app.get('/hello', (req, res) => { // URL 응답 테스트
    res.send('hello world !')
})

app.get('/error', (req, res) => {
  throw new Error('서버에 치명적인 에러가 발생했습니다.') // 에러발생
})

app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack) // 에러 메세지 출력
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

index.js 파일에 위와 같이 임의로 에러를 발생시키는 로직을 추가한다. 아래와 같이 사용자가 /error 라는 URL 주소로 접근했을때 에러를 발생시킨다. 발생된 에러는 모든 미들웨어를 건너띄고, "서버 내부 오류 처리"라는 주석으로 명시된 오류 처리 로직에서 처리된다. 

localhost:5000/error 주소로 접근한 경우
에러 메세지 출력

 

* CORS (Cross Origin Resource Sharing) 설정

CORS 란 자신이 속하지 않은 다른 도메인, 다른 프로토콜, 다른 포트에 있는 리소스를 요청하는 HTTP 방식이다. 

예를 들면, 같은 도메인에 속한 Naver 사이트에서 Naver 서버로 리소스를 요청하면 응답으로 요청한 리소스를 보내준다. 그러나 다른 도메인에 속한 Google 사이트에서 Naver 서버로 리소스를 요청하면 응답을 제한한다. 그 이유는 만약 이를 허용하면 나쁜 의도를 가진 해커나 사용자가 타 사이트에서 특정 서버로 악의적인 무차별적인 요청 트래픽을 보낼 수 있다. 그러면 서버가 다운된다. 주로 웹사이트 화면을 보여주는 프론트엔드 코드와 서버 코드는 같은 도메인 같은 포트에서 서비스된다. 물론 그렇지 않은 서비스들도 있다. 

그래서 주로 CORS 를 허용할 사이트만 등록하여 서버에 등록된 사이트들만 사용자 요청에 대한 응답을 허용하도록 한다. 다만 내가 구현한 서버가 아닌 OPEN API 서버에서 데이터를 가져올때 에러가 발생하면 프록시 서버를 만들어서 우회해야 한다. 

package.json

package.json 파일에 cors 패키지가 설치되었는지 확인한다.

서버가 구동중인지 확인한다.

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()
var cors = require('cors') 

var corsOptions = { // CORS 옵션
    origin: 'http://localhost:3000', // 해당 URL 주소만 요청을 허락함
    credentials: true // 사용자 인증이 필요한 리소스를 요청할 수 있도록 허용함
}

app.use(cors(corsOptions)) // CORS 설정

app.get('/hello', (req, res) => { // URL 응답 테스트
    res.send('hello world !')
})

app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack)
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

CORS 옵션에 localhost:3000 주소만 허용한다. 이렇게 하면 웹사이트에서 localhost:3000 이 아닌 주소로 접속한 경우 해당 서버는 요청을 블락(block) 한다. credentials: true 옵션은 브라우저에서 사용자 인증정보(쿠키)가 포함된 요청을 서버가 허용할지 말지를 결정한다. false 로 설정하면 서버는 브라우저가 보낸 요청에 응답하지 않고 거부한다. 

app.get('/hello', (req, res) => { // URL 응답 테스트
    res.json('hello world !')
})

서버 폴더의 엔트리 포인트(entry point) 파일인 index.js 에서 코드를 위와 같이 수정한다. res.send() 를 res.json() 으로 수정하였다. 이렇게 하면 서버는 응답본문(response body)을 html 형태로 보내는 대신 json 문자열로 전송한다. 

json 문자열
Network 탭 확인

 

 

CORS 요청의 자세한 로직(알고리즘)

브라우저는 먼저 서버에게 요청을 보내서 서버가 인증정보를 포함한 요청을 처리해줄수 있는지 물어본다. 이러한 브라우저 요청을 preflight request 라고 한다. 이때 HTTP 요청 메서드는 OPTIONS 이다. 즉, 먼저 서버한테 인증정보가 포함된 요청을 보내도 되는지 물어본다. 위와 같이 credentials: true 설정이 되어 있으면, 서버는 브라우저에게 보내도 된다고 응답을 보낸다. 브라우저는 이제 서버로부터 실제 데이터를 조회하거나 업데이트하기 위한 API 요청을 서버에게 보낸다. 이때 HTTP 메서드는 GET, POST, PUT, DELETE 이다. 그럼 서버는 해당 리소스를 찾아서 브라우저에게 전송한다. 만약 preflight 요청을 보냈을때 서버에 credentials: false 설정이 있으면, 서버는 브라우저 요청을 거부한다. 

credentials: true 로 설정하면 서버는 응답 헤더(response header)에 Access-Control-Allow-Credentials: true 로 설정하여 브라우저에게 인증정보가 포함된 요청을 보내도 된다고 알려준다. 

 

자세한 내용은 아래 블로그를 참고하도록 하자!

https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98

 

🍪 CORS 쿠키 전송하기 (withCredentials 옵션)

🤬 CORS를 허용했는데도 쿠키가 넘어가지 않는 현상 보통 웹을 구성할때 리액트(React)나 뷰(Vue)와 같은 라이브러리 / 프레임워크를 사용한다면 따로 프론트 서버를 실행하여 개발하게 된다. 만일

inpa.tistory.com

 

 

fetch 참고문서

 

Fetch API - Web API | MDN

Fetch API는 네트워크 통신을 포함한 리소스 취득을 위한 인터페이스가 정의되어 있습니다.  XMLHttpRequest와 같은 비슷한 API가 존재합니다만, 새로운 Fetch API는 좀더 강력하고 유연한 조작이 가능

developer.mozilla.org

 

* 리액트에서의 CORS 테스트 

이제 리액트 프로젝트에서 App.js 파일을 아래와 같이 작성하고 실행해보자!

import React, { Component } from 'react';

class App extends Component {
  state = {
    msg: ''
  }
  
  // DOM 이 HTML 문서에 렌더링된 직후 서버에 접속하여 데이터를 가져옴
  componentDidMount(){
    fetch('http://localhost:5000/hello')
    .then(res => res.json())
    .then(data => this.setState({ msg: data }))
  }
  render() {
    const { msg } = this.state 
    return (
      <div>
        {msg}
      </div>
    );
  }
}

export default App;

위 코드는 라이프사이클 메서드인 componentDidMount (DOM 이 HTML 문서에 렌더링된 직후)에 API URL 인 http://localhost:5000/hello 에 접속하고 해당 서버에서 데이터를 가져온다.

fetch 는 ajax 와 같은 비동기콜(async call)을 할때 사용되는 웹 API 이다. fetch 는 비동기로 동작하며, Promise 객체를 반환한다. 그러므로 서버에 리소스를 요청하고, 이후에 응답을 전달받으면 then 함수를 이용하여 응답 결과를 조회할 수 있다. 서버에서 res.json() 을 이용하여 json 문자열을 전송했으므로 브라우저에서도 res.json()을 이용하여 JSON 문자열을 파싱해서 다시 원본 데이터로 변환해준다.

이제 리액트 루트 URL 주소인 http://localhost:3000/ 에 접속하면 웹 화면이 초기 렌더링될때 서버에서 데이터를 가져와서 화면에 보여준다. 아래와 같이 개발자 도구의 네트워크 탭을 열어서 확인해보자! hello 라는 파일을 클릭해서 Headers 탭을 확인해보자!

Access-Control-Allow-Origin CORS 설정 확인

 

Response Headers 의 속성 중 Access-Control-Allow-Origin 에 우리가 설정한 http://localhost:3000 이 보인다.

만약 서버 cors 설정의 origin 주소를 아래와 같이 3001번으로 변경하면 어떻게 될까?

var corsOptions = { // CORS 옵션
  origin: 'http://localhost:3001', // 해당 URL 주소만 요청을 허락함
  credentials: true // 
}

서버 루트의 index.js 파일에서 해당하는 부분을 위와 같이 수정해보자! 그리고 서버를 다시 재시작해보자!

그런 다음 리액트 프로젝트 루트 URL 주소인 http://localhost:3000/ 으로 다시 접속해보자! 

CORS 에러
CORS 에러 2

위와 같이 CORS 에러를 출력한다. 서버에서는 http://localhost:3001 주소의 요청에 대해서만 응답을 보내주므로 http://localhost:3000 주소의 요청에 대해서는 응답을 거부하고 CORS 에러가 발생한다. 

 

순수 자바스크립트에서의 CORS 테스트 

fetch('http://localhost:5000/hello')
.then(res => res.json())
.then(data => console.log(data))

순수자바스크립트인 app.js 파일에서 위와 같이 작성해도 테스트가 가능하다. fetch 는 ajax 와 같은 비동기콜(async call)을 할때 사용되는 웹 API 이다. fetch 는 비동기로 동작하며, Promise 객체를 반환한다. 그러므로 서버에 리소스를 요청하고, 이후에 응답을 전달받으면 then 함수를 이용하여 응답 결과를 조회할 수 있다. 서버에서 res.json() 을 이용하여 json 문자열을 전송했으므로 브라우저에서도 res.json()을 이용하여 JSON 문자열을 파싱해서 다시 원본 데이터로 변환해준다.

이제 클라이언트의 Liver Server 포트주소를 확인해보자! 현재는 5501번이다. 이제 브라우저에서 Liver Server 주소인 http://localhost:5501/ 에 접속해보자! 

CORS 에러

위와 같이 CORS 오류가 발생한다. 이러한 에러가 발생하는 이유는 서버의 CORS 설정옵션에 origin: 'http://localhost:3000' 으로 되어 있기 때문에 클라이언트 서버주소가 "https://localhost:3000" 이 아닌 모든 요청에 대해서는 응답을 거부하기 때문이다. 즉, 현재 클라이언트 서버주소(localhost:5501)는 API 서버에서 허용하지 않는다. 

클라이언트에 있는 Live server 는 웹페이지(정적파일)를 로딩하기 위한 서버이다. 그리고 실제 API 서버는 브라우저가 요청한 사항을 처리하기 위한 서버이다. 헷갈리지 않도록 하자!

해결방법은 두가지가 있다. 첫번째로 클라이언트 서버(Live server)의 포트를 3000번으로 변경한다. 두번째 방법은 API 서버의 CORS 설정옵션에서 origin 주소를 Live server 의 포트주소(5501)로 변경해주면 된다. 후자로 CORS 에러를 해결해보자!

var corsOptions = {
  origin: 'http://localhost:5501',
  credentials: true 
}
var corsOptions = {
  origin: 'http://127.0.0.1:5501',
  credentials: true 
}

API 서버의 index.js 파일의 해당부분을 위와 같이 둘 중 하나로 수정하고 Ctrl + S 를 눌러서 서버를 재시작한다. 

CORS 에러 해결

웹 화면이 초기 렌더링될때 우리가 만든 API 서버에서 데이터를 가져와서 화면에 보여준다. 위와 같이 개발자 도구를 열어서 확인해보자!

위와 같이 개발자 도구의 네트워크 탭을 열어서 확인해보자! hello 라는 파일을 클릭해서 Headers 탭을 확인해보자! Response Headers 의 속성 중 Access-Control-Allow-Origin 이 CORS 설정옵션의 origin 설정이다.

페이로드 (Payload)

Response 탭에서 서버가 브라우저에게 전송한 JSON 문자열이 보인다. 이를 페이로드(payload) 라고 한다. 

 

 

OPTIONS 메서드 참고문서

 

OPTIONS - HTTP | MDN

HTTP OPTIONS method 는 목표 리소스와의 통신 옵션을 설명하기 위해 사용됩니다. 클라이언트는 OPTIONS 메소드의 URL을 특정지을 수 있으며, aterisk(*) 를 통해 서버 전체를 선택할 수 있습니다.

developer.mozilla.org

 

Access-Control-Allow-Origin 속성

 

브라우저가 서버에 리소스를 요청할때 곧바로 리소스를 보내주는 것이 아니라 맨 먼저 서버가 내 요청에 응답을 보내줄 수 있는지 확인하기 위해 브라우저는 초기 요청을 보낸다. 이를 사전요청(preflight request) 라고 한다. 이때 브라우저는 HTTP 메서드 중에서 OPTIONS 메서드를 사용한다.

브라우저는 위와 같이 사전요청에 대한 응답을 받아서 응답 헤더(Response Headers)의 Access-Control-Allow-Origin 속성값을 확인하고, 자신의 현재 URL 인 http://localhost:3000 과 다르기 때문에 CORS 에러를 출력한다. 

만약 두 주소가 동일하다면 이번에는 진짜 원하는 리소스를 얻기 위해 브라우저가 다시 한번 서버에 요청을 보내고 응답을 받는다. 이때는 HTTP 메서드 중에서 GET, POST, PUT, DELETE 중 하나를 사용한다. 

 

* 요청 본문(request body) 파싱 설정 및 테스트

import React, { Component } from 'react'
import logo from './logo.svg';
import './App.css';

class App extends Component{
  state = {
    msg: ''
  }
  componentDidMount(){
    fetch('http://localhost:5000/hello', {
        method: 'POST',
        body: JSON.stringify({
          userId: "test",
          email: "test@gmail.com"
        })
    })
    .then(res => res.json())
    .then(data => console.log(data))
  }

  render(){ 
    const { msg } = this.state 
    return (
      <div className='App'>
        {msg}
      </div>
    )
  }
}
export default App

리액트의 App.js 파일을 위와 같이 작성하자!

fetch('http://localhost:5000/hello', {
  method: 'POST',
  body: JSON.stringify({
    userId: "test",
    email: "test@gmail.com"
  })
})
.then(res => res.json())
.then(data => console.log(data))

fetch API 를 이용하여 브라우저에서 서버로 비동기 요청을 보낸다. 이때 HTTP 메서드는 POST 이다. 이렇게 하면 아래와 같이 브라우저가 전송하는 데이터(페이로드)를 개발자 도구에서 확인할 수 있다. 폐이로드는 요청본문(request body)에 실려서 전송된다. 서버에서 요청본문은 req 객체의 body 프로퍼티로 조회가 가능하다. 

app.post('/hello', (req, res) => { // POST 요청 테스트 
  console.log(req.body)
  res.json({ userId: req.body.userId, email: req.body.email })
})

server > index.js 파일에 위의 코드를 추가한다. 

fetch api 로 전송하는 페이로드

하지만 아래와 같이 서버오류가 발생한다. 이유는 서버에서는 브라우저가 전송한 요청본문(페이로드)를 파싱(해석)하지 못하기 때문이다. 에러 메세지를 확인해보면 userId 를 조회하지 못하는데 이는 req 객체의 body 프로퍼티가 없기 때문이다.

Internal Server Error

 

해결방법은 아래와 같이 요청본문(request body)를 파싱(해석)해서 데이터를 조회할 수 있는 설정이 필요하다. 또한, /hello 라는 URL 주소로 POST 요청을 보낼때 이를 처리할 라우트 핸들러가 필요하다. 그래서 아래와 같이 코드를 추가한다. 

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()
var cors = require('cors') 

var corsOptions = { // CORS 옵션
    origin: 'http://127.0.0.1:5501',
    credentials: true
}

app.use(cors(corsOptions)) // CORS 설정
app.use(express.json()) // request body 파싱

app.get('/hello', (req, res) => { // URL 응답 테스트
  res.json('hello world !')
})
app.post('/hello', (req, res) => { // POST 요청 테스트 
  console.log(req.body)
  res.json({ userId: req.body.userId, email: req.body.email })
})
app.get('/error', (req, res) => { // 오류 테스트 
  throw new Error('서버에 치명적인 에러가 발생했습니다.')
})

// 폴백 핸들러 (fallback handler)
app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack)
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

참고 자료

 

Request param,query, body 의 차이점

Request 객체는 API를 컨트롤하기 위한 메소드를 셋 담고 있다.

medium.com

요청 본문(request body)은 주로 Ajax콜과 같은 비동기 POST 요청을 JSON 문자열로 서버에 전송한다. express 서버에서는 req 객체의 body 프로퍼티에 접근하여 값을 가져올 수 있다. 

JSON 문자열 참고자료

 

JSON.stringify() - JavaScript | MDN

JSON.stringify() 메서드는 JavaScript 값이나 객체를 JSON 문자열로 변환합니다. 선택적으로, replacer를 함수로 전달할 경우 변환 전 값을 변형할 수 있고, 배열로 전달할 경우 지정한 속성만 결과에 포함

developer.mozilla.org

자바스크립트의 모든 데이터 타입(특히 객체나 배열)은 서버로 전송할때 JSON 문자열 형태로 바꿔서 전달해야 된다. 

다시 브라우저에서 /hello 주소로 POST 요청을 보내면 위와 같이 status 코드가 200 으로 설정되면서 정상적인 응답을 받는다. 하지만 아래와 같이 서버에서 전송한 데이터는 빈 객체를 반환한다. 

 

fetch('http://localhost:5000/hello', {
  method: 'POST',
  headers: { // 해당 설정이 없으면 서버에서 데이터 조회 불가능
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    email: "test@gmail.com",
    userId: "test"
  })
})
.then(res => res.json())
.then(data => console.log(data))

해결방법은 위와 같이 fetch 로 POST 요청을 보낼때 headers 옵션에 'Content-Type': 'applicaiton/json' 을 반드시 설정해줘야 된다. 이유는 서버에 설정된 express.json() 미들웨어는 아래와 같이 브라우저에서 보낸 요청 헤더(request headers)에 반드시 'Content-Type': 'applicaiton/json' 이 설정되어 있어야 요청본문(페이로드)를 파싱해서 데이터를 조회할 수 있다. 

만약 브라우저에서 fetch 로 POST 요청을 보낼때 headers 옵션을 설정하지 않으면 아래와 같이 요청 헤더(request header)에는 Content-Type 이 text/plain 으로 설정되어 서버에서는 브라우저가 보낸 페이로드(데이터)를 조회하지 못한다. 

이제 서버에서는 브라우저가 요청본문(request body)에 실어서 전송한 페이로드(데이터)를 조회할 수 있다. 즉, req.body 객체가 존재한다. 

app.post('/hello', (req, res) => { // POST 요청 테스트 
  console.log(req.body)
  res.json({ userId: req.body.userId, email: req.body.email })
})

서버는 요청본문을 전달받아서 res.json() 메서드를 이용하여 사용자 정보를 다시 JSON 형태의 문자열 형태로 브라우저에게 전송한다. 브라우저는 아래와 같이 서버가 보낸 응답을 확인할 수 있다. 

 

결론적으로 서버에서 요청본문(requeust body)의 데이터를 조회하려면 app.use(express.json()) 설정이 필요하고, 해당 설정을 하면 요청헤더(reqest header)의 Content-Type: application/json 인 경우 요청본문을 파싱해서 페이로드를 req.body 객체에 주입한다. 

 

* 로거 (Logger) 설정

로거(logger)는 말 그대로 각 브라우저의 요청을 서버의 로그(log)로 남기는 것이다. 로그에는 요청 메서드(GET, POST 등), 요청주소(URL), 상태코드(status code), 요청에 걸린 시간 등이 있다. 

npm install morgan

반드시 package.json 파일이 위치한 server 폴더 안에서 설치하도록 한다. 

package.json 파일에서 설치를 확인한다. 

var logger = require('morgan')
app.use(logger('tiny'))

위 코드를 index.js 파일에 추가한다. 

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()
var cors = require('cors') 
var logger = require('morgan')

var corsOptions = { // CORS 옵션
    origin: 'http://127.0.0.1:5501',
    credentials: true
}

app.use(cors(corsOptions)) // CORS 설정
app.use(express.json()) // request body 파싱
app.use(logger('tiny')) // Logger 설정 

app.get('/hello', (req, res) => { // URL 응답 테스트
  res.json('hello world !')
})
app.post('/hello', (req, res) => { // POST 요청 테스트 
  console.log(req.body)
  res.json({ userId: req.body.userId, email: req.body.email })
})
app.get('/error', (req, res) => { // 오류 테스트 
  throw new Error('서버에 치명적인 에러가 발생했습니다.')
})

// 폴백 핸들러 (fallback handler)
app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack)
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

브라우저에서 /hello 주소로 POST 요청을 보내서 서버의 로그에 기록되었다. 

 

* 미들웨어 함수(middleware)

// 사용자 요청이 들어온 경우 공통적으로 처리되어야 하는 미들웨어 함수 
app.use(cors(corsOptions)) // CORS 설정
app.use(express.json()) // request body 파싱
app.use(logger('tiny')) // Logger 설정

위의 코드는 브라우저 요청이 서버에 도달할때마다 공통적으로 처리되어야 되는 미들웨어(middleware) 함수이다. 미들웨어란 브라우저에서 요청이 생성되어 서버로 전달된 다음 다시 브라우저로 응답을 전송하기까지 중간에 처리되는 일련의 과정을 함수로 정의한 것이다. 

 

* Mongo DB 연동하기

npm install mongoose

mongoose 패키지는 ODM(Object Document Mapper)이며 뜻은 Mongo DB 의 데이터베이스 모델링과 데이터 쿼리(검색)를 자바스크립트 언어로 사용할 수 있도록 해주는 API를 제공한다. 그래서 이제는 Mongo DB 를 직접 조작하는게 아니라 Node.js에서 자바스크립트를 이용하여 조작이 가능하다.

var mongoose = require('mongoose')

const CONNECT_URL = 'mongodb://localhost:27017/syleemomo'
mongoose.connect(CONNECT_URL, { // Mongo DB 서버 연결
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => console.log("mongodb connected ..."))
  .catch(e => console.log(`failed to connect mongodb: ${e}`))

위와 같은 코드를 추가한다. 몽고 DB 주소 (CONNECT_URL) 에서 localhost 는 아래와 같이 디폴트 값으로 ipv6 (IP 버전 6)로 설정되기 때문에 몽고 DB 연동에 실패하면 CONNECT_URL 주소에서 localhost 를 127.0.0.1 로 수정한다.

몽고 DB 에러 해결하기 1

 

Mongoose 6.0 버전 이상에서는 아래와 같이 설정 옵션들은 기본적으로 true 로 설정되기 때문에 위 코드를 추가하고 오류가 일어난다면 아래와 같이 설정옵션들을 제외하면 오류가 해결된다. 

몽고 DB 에러 해결하기 2

var mongoose = require('mongoose')

const CONNECT_URL = 'mongodb://localhost:27017/syleemomo'
mongoose.connect(CONNECT_URL)
.then(() => console.log("mongodb connected ..."))
.catch(e => console.log(`failed to connect mongodb: ${e}`))
var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()
var cors = require('cors') 
var logger = require('morgan')
var mongoose = require('mongoose')

var corsOptions = { // CORS 옵션
    origin: 'http://127.0.0.1:5501',
    credentials: true
}
const CONNECT_URL = 'mongodb://localhost:27017/syleemomo'
mongoose.connect(CONNECT_URL)
.then(() => console.log("mongodb connected ..."))
.catch(e => console.log(`failed to connect mongodb: ${e}`))

app.use(cors(corsOptions)) // CORS 설정
app.use(express.json()) // request body 파싱
app.use(logger('tiny')) // Logger 설정 

app.get('/hello', (req, res) => { // URL 응답 테스트
  res.json('hello world !')
})
app.post('/hello', (req, res) => { // POST 요청 테스트 
  console.log(req.body)
  res.json({ userId: req.body.userId, email: req.body.email })
})
app.get('/error', (req, res) => { // 오류 테스트 
  throw new Error('서버에 치명적인 에러가 발생했습니다.')
})

// 폴백 핸들러 (fallback handler)
app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack)
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

전체코드는 위와 같다. 반드시 Mogo DB Compass를 열어서 자신의 데이터베이스 주소를 확인하고, Mongo DB 서버를 실행하고 Connect한 다음에 express 서버를 시작해야 연결이 제대로 된다.

몽고 DB 서버가 구동중인지 확인이 필요하다. 서버가 구동중이 아니면 API 서버에서 몽고 DB에 연결할때 오류가 발생한다. CMD 창을 관리자 권한으로 실행하고 net start MongoDB 를 입력했을때 위와 같이 이미 시작되었다는 메세지가 나오면 몽고 DB 서버가 이미 구동중이라는 의미다. 즉, API 서버에서 몽고 DB 서버에 연결 가능하다. 

 

* 외부 API 서버 접속하기 - OPEN API 요청 보내기

npm install axios

위 명령어를 실행하여 외부 API 접속에 필요한 axios 모듈을 설치한다. 

package.json

 

var axios = require('axios')

index.js 파일에 위 코드를 추가한다. axios 는 fetch 와 동일한 기능을 한다. 서버로 비동기 요청을 보낸다. 다만 axios 는 fetch 와 다르게 크로스 브라우징을 지원한다. 

app.get('/fetch', async (req, res) => {
  const response = await axios.get('https://jsonplaceholder.typicode.com/todos')
  res.send(response.data)
})

index.js 파일에 위 코드도 추가한다. /fetch URL 주소로 접속하면 외부 API 서버에서 할일목록에 대한 데이터를 가져와서 브라우저로 응답을 전달한다. async, await 은 비동기 콜을 좀 더 쉽게 사용할 수 있게 해준다. 즉, 외부 API 서버에서 결과를 가져올때까지 기다렸다가 결과가 도착하면 response 변수에 담아서 브라우저로 응답을 보낸다. 

 

현재까지 구현한 index.js 파일은 아래와 같다.  

var express = require('express') // node_modules 내 express 관련 코드를 가져온다
var app = express()
var cors = require('cors') 
var logger = require('morgan')
var mongoose = require('mongoose')
var axios = require('axios')

var corsOptions = { // CORS 옵션
    origin: 'http://127.0.0.1:5501',
    credentials: true
}
const CONNECT_URL = 'mongodb://localhost:27017/syleemomo'
mongoose.connect(CONNECT_URL)
.then(() => console.log("mongodb connected ..."))
.catch(e => console.log(`failed to connect mongodb: ${e}`))

app.use(cors(corsOptions)) // CORS 설정
app.use(express.json()) // request body 파싱
app.use(logger('tiny')) // Logger 설정 

app.get('/hello', (req, res) => { // URL 응답 테스트
  res.json('hello world !')
})
app.post('/hello', (req, res) => { // POST 요청 테스트 
  console.log(req.body)
  res.json({ userId: req.body.userId, email: req.body.email })
})
app.get('/error', (req, res) => { // 오류 테스트 
  throw new Error('서버에 치명적인 에러가 발생했습니다.')
})
app.get('/fetch', async (req, res) => {
  const response = await axios.get('https://jsonplaceholder.typicode.com/todos')
  res.send(response.data)
})

// 폴백 핸들러 (fallback handler)
app.use( (req, res, next) => {  // 사용자가 요청한 페이지가 없는 경우 에러처리
    res.status(404).send("Sorry can't find page")
})
app.use( (err, req, res, next) => { // 서버 내부 오류 처리
    console.error(err.stack)
    res.status(500).send("something is broken on server !")
})
app.listen(5000, () => { // 5000 포트로 서버 오픈
    console.log('server is running on port 5000 ...')
})

http://localhost:5000/fetch URL 주소에 접속해서 할일목록을 제대로 가져오는지 테스트해보자! 그전에 서버가 켜져 있는지 다시 한번 확인하자!

외부 API 주소에서 할일목록 가져오기

위와 같이 외부 API 서버로부터 할일목록을 가져올 수 있음을 확인할 수 있다. 

 

728x90