자바스크립트 공부 일지 8

Node.js 입문 1주차 - express

Express

node.js의 경우 웹 서버 프레임워크로 express, nest.js 등을 이용합니다. node.js로도 웹 서버는 구현할 수 있지만 이를 기반으로 추가적으로 간편하고 빠르게 작업할 수 있게 보완한 것이 프레임워크입니다.

다음은 Express의 특징입니다.

  • Express.js는 Node.js로 서버를 빠르고 간편하게 만들 수 있도록 도와주는 웹 프레임워크입니다.
  • Express.js 이외에 다양한 웹 프레임워크가 존재하나, 현재로선 가장 많이 사용되었습니다.
  • 최근에는 Nest.js가 각광받고 있습니다.

웹서버와 차이

  • Express.js와 웹서버는 다릅니다.
  • Express.js는 웹서버가 자체가 아닌 Node.js기반 웹서버 구현을 위한 웹 프레임워크입니다.

환경 구성

프로젝트 폴더 구성

spa_mall 이라는 폴더를 생성합니다.

npm init

package.json 파일을 생성합니다.

npm i express

express 패키지를 설치합니다.

서버 실행하기

js
//app.js
const express = require('express');
const app = express();
const port = 3000;

// 3000 번 포트로 get 메서드 실행 시 Hello World 응답.
app.get('/', (req, res) => {
  res.send('Hello World!');
});

// 3000 번 포트로 요청을 보낼 수 있도록 포트를 열어줍니다.
app.listen(port, () => {
  console.log(port, '포트로 서버가 열렸어요!');
});

이후 브라우저에서 해당 URL을 요청하면 Hello World 텍스트를 보실 수 있습니다.

만일 포트가 사용중인것을 확인하고 싶은 경우 아래 명령으로 사용중인 포트를 조회할 수 있습니다.

bash
lsof - i :[포트명]

그리고 아래와 같이 포트를 중지할 수 있습니다.

bash
kill -9  :[PID명]

API client

API clients란 개발단계에서 우리가 작성한 API의 요청을 확인하거나 테스팅 할 때 도움을 주는 툴입니다. API Client를 사용함으로 개발 속도를 높이고 치명적인 에러를 예방하는데 도움을 받을 수 있습니다.

따로 프론트엔드를 구성하지 않고도 테스트가 가능한거죠.

관련 프로그램은 다음과 같습니다.

  • Postman, Insomnia, Thunder Client
  • Thunder Client의 경우 VS Code내에서 사용할 수 있습니다. 물론 위와 같은 툴도 VS Code에 설치해 사용할 수 있지만 지금은 Thunder Client를 사용하겠습니다.

현재는 HTTP MethodGET Method에 대응하는 API만 생성했습니다. 이후 POST, PATCH, PUT, DELETE, HEAD등 다양한 Method에 대한 API를 개발하고 테스트하기 위해 사용할 수 있습니다.
Extensions탭에서 검색해 설치해봅시다.

사용방법은 Postmap과 비슷합니다. Query탭에서 파라미터도 보낼 수 있고요. POST, PUT메서드 이용시 body에 데이터를 담아 보낼텐데, 이때에도 Body탭에 json 방식으로 데이터를 전달할 수 있습니다.

또 좌측에 Collections탭에서는 여러가지의 API를 그룹화 할 수 있습니다.

Env탭에는 여러번 사용되는 값들을 환경변수로 사용할 때 사용합니다. Token, URL, 개인 키 등 다양한 자격증명 저장 및 사용할 수 있습니다. [그림 첨부 예정]

Routing 이해하기

Routing은 클라이언트의 요청 조건(메서드, 주소 등)에 대응해 응답하는 방식을 말합니다. Router는 클라이언트 요청을 쉽게 처리 할 수 있게 도와주는 Express.js기본 기능중 하나입니다.

일반적으로 Router는 아래와 같은 구조를 가집니다.

js
router.METHOD(PATH, HANDLE);
  • router: express의 라우터 정의
  • mehtod: HTTP Method
  • path: 실제 서버에서 API를 사용하기 위한 경로
  • handler: 라우트가 일치할 때 실행되는 함수

우선 프로젝트 폴더내 router폴더 생성 후 아래와 같이 goods모듈(특정 부분을 공통 처리할 API)을 생성하고 app.js에서 불어와 사용해 봅시다.

js
// goods.js
const express = require("express"); // express 라이브러리 할당
const router = express.Router(); // core Router를 반환받아 사용

// API 생성
// app.js에 app.use('/api', 현재 미들웨어)로 적용
// locaklhost:3000/api/
router.get('/',(req, res) => {
  // 응답을 보낸다.
  res.send("default url for goods.js GET Method");
})

// about url로 get 요청 들어오는 경우
// locaklhost:3000/api/about
router.get('/about', (req, res) => {
  res.send("goods.js about PATH");
})

// 밖에서 사용할 수 있도록 exports해준다.
module.exports = router;

아래와 같이 app.js에서 이 미들웨어를 사용할 수 있다.

js
// app.js
const express = require('express');
const app = express();
const port = 3000;
const goodsRouter = require('./routes/goods.js')

app.use

app.get('/', (req, res) => {
  res.send('Hello World!');
});

// localhost:3000/api/ -> goodsRouter
// app.use에 인터프리터가 도달했을 때,
// "/api" 라는 경로로 요청이 온다면 이 goodsRouter 미들웨어를 우선 탐색하라는 의미
// 즉 goods 미들웨어를 탐색함. 전역 미들웨어.
app.use("/api", goodsRouter);
// 아래와 같이 배열내 미들웨어를 넣어 실행할 수 있다.
// app.use("/api", [goodsRouter, userRouter]);

app.listen(port, () => {
  console.log(port, '포트로 서버가 열렸어요!');
});

Module 이해하기

  • Module은 분리된 자바스크립트 파일이며 각 파일은 특정한 기능을 가진 여러 개의 함수와 변수들의 집합입니다.
  • Module을 만들면 다른 프로그램에서 해당 모듈을 재사용할 수 있습니다.
  • Module은 그 자체로도 하나의 프로그램이면서 다른 프로그램의 부품으로도 사용할 수 있습니다.
  • 보통 1개의 파일이 1개의 모듈이 됩니다.

Module의 메서드

  • export: 명령어를 변수나 앞에 붙이면 외부 모듈에서 해당 변수나 함수에 접근 가능
  • import, require: 외부 모듈을 불러올 수 있습니다. require는 현재 학습한 CommomJS로 모듈 시스템을 관리할 때, import는 ES6로 모듈 시스템을 관리할 때 유용합니다. 이는 pakage.json에서 확인할 수 있습니다. 파일내 적혀있지는 안지만 기본값으로 type값이 CommonJS입니다. 때문에 현재 예제에서는 require를 사용합니다. 아래와 같이 ES6로 변경된다면 import를 사용합니다.
json
// package.json
{
  "type": "ES6"
}

모듈 자체를 바로 add 함수 할당

우선 프로젝트 폴더내 modules폴더를 생성하고 math.js모듈에 두 값을 합하는 함수를 생성합니다.

js
// math.js
function add(a, b) {
  return a + b;
}

module.exports = add;

이제 math.js모듈을 불러오고 함수를 실행해봅시다.

js
// run.js
const add = require("./math.js") // math.js 모듈을 가져온다.

console.log(add(10, 30));
// 40

익명함수 하나만 외부 사용

아래와 같이 exports 명령을 이용해 특정 함수만은 외부 에서 접근가능하도록 할 수 있습니다. 단, 객체로서 내보내게 됩니다.

js
// math.js
exports.add = function (a, b) {
  return a + b;
}

// module.exports = add;

따라서 아래와 같이 객체 분해 할당 하여 간단하게 사용할 수 있습니다.

js
// const add = require("./math.js") // { add: [Function (anonymous)]}
const {add} = require("./math.js") // 객체 ㅂ

console.log(add(10, 30));
// 40

모듈을 호출했을 때 키 값에 함수 할당해 외부 사용

js
// math.js
function add (a, b) {
  return a + b;
}

// 객체 형식으로 내보내기
module.exports = {add: add};

함수표현식으로 할당해 외부 사용

js
const add = (a, b) => {
  return a + b;
}

exports.add = add;

Request와 Response

  • Request란 클라이언트가 서버에게 전달하려는 정보나 메시지를 담는 객체를 의미.
  • Response란 서버에서 클라이언트로 응답 메시지를 전송시켜주는 객체

request

  • eq.app : req 객체를 통해 app 객체에 접근할 수 있습니다.
  • req.ip: 요청한 Client의 ip 주소가 담겨 있습니다.
  • req.body: Request를 호출할 때 body로 전달된 정보가 담긴 객체입니다.
    • express.json() Middleware를 이용하여야 해당 객체를 사용할 수 있습니다.
  • req.params: 라우터 매개 변수에 대한 정보가 담긴 객체입니다.
  • req.query: Request를 호출할 때 쿼리 스트링으로 전달된 정보가 담긴 객체입니다.
  • req.cookies: Request를 호출할 때 Cookie 정보가 담긴 객체입니다.
    • cookie-parser Middleware를 이용하여야 해당 객체를 사용할 수 있습니다.
  • req.get(Header): 헤더에 저장된 값을 가져오고 싶을 때 사용합니다.
query
js
const goodsRouter = require('./routes/goods.js')

// req.body() 사용을 위해 작성
// bodyParser 미들웨어를 사용하기 위해 선언
// 모든코드에서(경로를 지정X) bodyparser를 선언해 req.body에 데이터를
// 정상적으로 보기위해 사용
app.use(express.json())

app.get('/', (req, res) => {
  console.log(req.query);

  res.send('정상적으로 변환되었습니다.')
});
// [1] localhost:3000?queryKey=valuevalue 요청!
// [2] { queryKey: 'valuevalue' } 출력
param
js
// ":id " -> localhost:3000/[값] -> [값]의 키는 id로 지정해서 받겠다.
app.get("/:id", (req, res) => {
  console.log(req.params);

  res.send(":id URI에 정상적으로 반환되었습니다.")
})

// [1] localhost:3000/helloworld 요청!
// [2] { id: 'helloworld' } 출력
body
js
app.post("/", (req, res) => {
  console.log(req.body);

  res.send("기본 URI에 POST메서드가 정상적으로 실행되었습니다.")
})

// [1] localhost:3000 URI로 post 요청. body내 { "id" : "value" } 형식으로 데이터 전송
// [2] { key1234: '안녕하세요 key1234입니다.' } 출력

response

  • res.app : res 객체를 통해 app 객체에 접근할 수 있습니다.
  • res.status(코드) : Response에 HTTP 상태 코드를 지정합니다.
  • res.send(데이터) : 데이터를 포함하여 Response를 전달합니다.
  • res.json(JSON) : JSON 형식으로 Response를 전달합니다.
  • res.end() : 데이터 없이 Response를 전달합니다.
  • res.direct(주소) : 리다이렉트할 주소와 함께 Response를 전달합니다.
  • res.cookie(Key, Value, Option) : 쿠키를 설정할 때 사용합니다.
  • res.clearCookie(Key, Value, Option) : 쿠키를 제거할 때 사용합니다.
json

JSON 형식으로 Response를 전달합니다. 요청한 브라우저는 Response로 Json을 받습니다.

js
app.get('/', (req, res) => {
  console.log(req.query);

  const obj = {
    "KeyKey" : "value 입니다.",
    "이름입니다." : "이름일까요?",
  }

  res.json(obj);
});

// 브라우저는 아래와 같은 json을 얻습니다.
// {
//   "KeyKey": "value 입니다.",
//   "이름입니다.": "이름일까요?"
// }
status

response가 정상적인지 비정상적인지 보낼 수 있습니다.

js
app.get('/', (req, res) => {
  console.log(req.query);

  const obj = {
    "KeyKey" : "value 입니다.",
    "이름입니다." : "이름일까요?",
  }

  res.status(400).json(obj);
});

// 브라우저는 Bad Request 400 status를 전달받습니다.

API와 REST API의 개념

API

  • API는 애플리케이션끼리 연결해주는 매개체이자 약속이라고 볼 수 있습니다.
  • 키보드로 글자를 입력하면 키보드는 우리가 작성한 글자를 컴퓨터에 전달해주는 역할을 합니다. 우리가 키보드의 키를 누르는것이 API를 호출하는것으로 볼 수 있습니다.
  • 웹 어플리케이션(프론트엔드)에서 원하는 기능을 수행하는 URL과 인터페이스를 제공한다는 의미입니다.
  • 우리가 작성 할 API에서 원하는 데이터를 받아 데이터베이스에 데이터를 저장하고, 저장되어 있는 데이터를 읽어서 웹 어플리케이션(프론트엔드)에 데이터를 제공하는 행위를 통해 사용자가 원하는 목적을 이룰 수 있게 해야 합니다.

REST API

  • REST(Representational State Transfer)는 월드 와이드 웹과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식입니다.
  • 간단하게 설명하자면 URL, Headers, Method 등 네트워크 표현 수단을 사람이 봐도 이해하기 쉬운 표현으로 정의한다고 보면 됩니다.
  • “REST 아키텍쳐”는 사람이 봐도 쉽게 이해할 수 있도록 “자원”을 정의하고 이 “자원”을 중심으로 표현을 구성하는 원칙을 제시합니다.
  • 클라이언트가 HTTP 메소드를 이용해 REST API를 호출하고, REST API는 데이터베이스에 데이터를 저장/가공하기 위해 데이터베이스에 데이터를 요청해 전달 받고 REST API는 JSON 혹은 XML을 클라이언트에게 전송하죠.
  • REST API는 “REST 아키텍쳐”라는 규칙을 따르는 API라고 생각하시면 됩니다.

자원(Resource) - URL : 우리가 만들 소프트웨어가 관리하는 모든 것을 자원으로 표현할 수 있습니다. 쇼핑몰이라면 상품(Goods)에 대해서 정보를 관리할것이고 또는 장바구니(Carts)에 담긴 상품들도 관리해야겠죠. 행위 - HTTP method : GET 메소드는 해당 자원의 조회, POST 메소드는 해당 자원의 생성 등 목적에 의해 사용된다고 보시면 됩니다. 이렇게 나누어진 것을 보통 CRUD 라고 합니다. 자원에 대한 생성/조회/수정/삭제를 각각의 method 로 나누어놓은 것이죠.

text
Create : 생성(POST)
Read : 조회(GET)
Update : 수정(PUT),(PATCH)
Delete : 삭제(DELETE)

표현 : 해당 자원을 어떻게 표현할지에 대한 설명입니다. 보통 JSON, XML 같은 형식을 이용해서 자원을 표현 HTTP에서는 Content-Type 이라는 헤더를 통해 표현 방법을 서술

다음 코드는 REST API예시입니다.

js
router.get('/books', (req, res) => {
	res.json({ success: true, data: getAllBooks() });
});
  • /books 라는 URL을 통해 전체 책 목록을 불러와 응답해 주는 역할을 하는 API입니다.
  • URL로 리소스를 구분할 수 있고 다른 표현이 없으므로 전체 리스트를 불러오는것을 짐작 할 수 있습니다. CRUD 중 Read를 담당하는 HTTP 메소드로 표현하여 REST한 API라고 볼 수 있습니다.

간단한 REST API 만들어보기 - 굿즈 상품 목록 검색하기

아래와 같이 goods.js모듈에 굿즈 상품 목록을 검색할 수 있는 API를 생성해봅시다.

js
const express = require("express");
const router = express.Router();

const goods = [
  {
    goodsId: 4,
    name: "상품 4",
    thumbnailUrl:
      "https://cdn.pixabay.com/photo/2016/09/07/02/11/frogs-1650657_1280.jpg",
    category: "drink",
    price: 0.1,
  },
  {
    goodsId: 3,
    name: "상품 3",
    thumbnailUrl:
      "https://cdn.pixabay.com/photo/2016/09/07/02/12/frogs-1650658_1280.jpg",
    category: "drink",
    price: 2.2,
  },
  {
    goodsId: 2,
    name: "상품 2",
    thumbnailUrl:
      "https://cdn.pixabay.com/photo/2014/08/26/19/19/wine-428316_1280.jpg",
    category: "drink",
    price: 0.11,
  },
  {
    goodsId: 1,
    name: "상품 1",
    thumbnailUrl:
      "https://cdn.pixabay.com/photo/2016/09/07/19/54/wines-1652455_1280.jpg",
    category: "drink",
    price: 6.2,
  },
];

//localhost:3000/api/goods
router.get("/goods", (req, res) => {
  // goods: goods 키 벨류 동일한 이름으로 goods로 축약
  res.status(200).json({goods})
})

router.get("/goods/:goodsId", (req, res) =>{
  // req.params 는 객체 형태로 전송된다. 객체 분해 할당을 이용하면 값을 바로 가져올 수 있다.
  const {goodsId} = req.params;
  console.log(goodsId); // 값만이 표기된다.

  // let result = null;
  // for(const good of goods){
  //   // 배열 요소를 순회해서 Id 값이 같은 경우의 객체만 result에 할당
  //   if( Number(goodsId) === good.goodsId){
  //     result = good;
  //   }
  // }

  // 배열내 객체 순회
  const [result] = goods.filter((good) => Number(goodsId) === good.goodsId)

  res.status(200).json({ detail: result });
})

module.exports = router;

브라우저는 localhost:3000/api/goods/1과 같은 URL을 GET메서드 요청한다면 특정 굿즈를 검색할 수 있게됩니다. 아래와 같이 Response탭에 상품의 정보가 담겨오는거죠.

js
{
  "detail": {
    "goodsId": 1,
    "name": "상품 1",
    "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/19/54/wines-1652455_1280.jpg",
    "category": "drink",
    "price": 6.2
  }
}