Express 기본 익히기

Jeris·2023년 5월 6일
0

코드잇 부트캠프 0기

목록 보기
87/107

1. Express란?

Express는 Node.js 환경의 서버 프로그램을 만들 때 사용하기 위한 빠르고(Fast) 개방적인(unopinionated) 간결한(minimalist) 웹 프레임워크입니다.

$ npm install express --save

2. API 서버란?

Web Server

Web server는 웹 페이지를 response body에 담아서 보내주는 서버입니다.

API Server

API server는 요청을 처리하고 처리한 결과를 response body에 담아서 보내주는 서버입니다.

오늘날 많은 API server들은 작업 결과를 이러한 JSON 형식으로 response body에 담아서 보냅니다.


3. Express 사용해보기

import express from "express";

const app = express();

app.get("/hello", (req, res) => {
  res.send("<h1>Hello Express</h1>");
});

app.listen(3000, () => {
  console.log("Server is listening...");
});

route handler 라우팅 작업을 하는 콜백을 의미합니다.
listen() 메서드의 두 번째 파라미터로 전달된 콜백은 서버가 외부의 리퀘스트를 들을 준비를 마치고 나면 자동으로 실행합니다.
localhost 컴퓨터의 자기 자신을 가리키는 루프백(loopback) 주소인 127.0.0.1의 호스트명입니다.


4. 직원 정보 조회하기

import express from "express";
import members from "./members.js";

const app = express();

app.get("/api/members", (req, res) => {
  res.send(members);
});

app.get("/api/members/:id", (req, res) => {
  const { id } = req.params;
  const member = members.find((m) => m.id === Number(id));
  if (member) {
    res.send(member);
  } else {
    res.status(404).send({ message: "There is no such member" });
  }
});

app.listen(3000, () => {
  console.log("Server is listening...");
});

route parameter 웹 애플리케이션에서 동적인 URL을 처리하기 위한 기능입니다. 일반적으로 URL에 특정 값을 변수로 사용해야 할 때 사용됩니다. 위의 예시에서 :id 같이 변수를 나타내는 토큰인 콜론(:)으로 시작합니다.

Route parameter에 접근하려면 request의 params 객체에 접근하면 됩니다.

Route parameter로 받은 id 변수는 문자열이므로 숫자로 사용할 때에 Number 함수를 써야합니다

실패한 response의 message를 보여줄 때에도 문자열이 아닌 JSON 형태의 객체로 표현하는 것이 좋습니다. res.send() 메서드는 자동으로 전달된 값을 JSON 문자열로 변환하여 HTTP 응답 본문으로 설정합니다.


5. 리소스란?

Resource(리소스)는 웹 애플리케이션에서 클라이언트가 요청하는 대상을 말합니다. 리소스는 일반적으로 URI(Uniform Resource Identifier)로 식별되며, 웹 애플리케이션에서는 이를 관리하기 위한 RESTful API를 사용합니다.

리소스는 웹 애플리케이션에서 제공하는 모든 것을 포함할 수 있습니다. 예를 들어, HTML 문서, 이미지, 동영상, JSON 데이터 등 다양한 형태의 데이터나 파일, 기타 웹 애플리케이션에서 사용되는 자원들을 모두 리소스로 간주할 수 있습니다.

RESTful API는 리소스에 대한 CRUD(Create, Read, Update, Delete) 연산을 HTTP 메서드(GET, POST, PUT, DELETE)와 연결하여 정의합니다. 이를 통해 클라이언트는 RESTful API를 통해 서버에서 제공하는 리소스를 생성, 조회, 수정, 삭제할 수 있습니다.


6. 특정 팀만 조회하기

Query

Query(쿼리)는 웹 애플리케이션에서 클라이언트가 서버에게 전달하는 매개변수(Parameter)를 말합니다. Query는 URI(Uniform Resource Identifier)에 포함될 수 있으며, 일반적으로 URL의 끝에 ?를 사용하여 표시하고, key=value 형태로 표현합니다.

req.query

app.get("/api/members", (req, res) => {
  const { team } = req.query;
  if (team) {
    const teamMembers = members.filter((m) => m.team === team);
    res.send(teamMembers);
  } else {
    res.send(members);
  }
});

req.query 객체는 query string을 파싱하여 { team: engineering, ... }와 같은 형태로 저장합니다.


7. POST 리퀘스트를 보내는 방법

get 메서드

express()로 생성한 객체의 get() 메서드는 특정 경로로 오는 GET 메서드를 가진 리퀘스트를 처리하는 라우트 핸들러를 등록합니다. POST 메서드처럼 다른 리퀘스트를 보내도 get() 메서드의 라우트 핸들러들은 실행되지 않습니다.

post 메서드

POST 리퀘스트의 경우에는 새로 추가하려는 리소스의 내용이 해당 request body에 담겨있어서 서버에서 별도로 처리해줘야 합니다.

app.post("/api/members", (req, res) => {
  console.log(req.body);
});

일반 브라우저에서는 리소스를 request body에 담을 수 없기 때문에 여기서는 VScode의 rest client라는 플러그인을 사용했습니다.

rest client

.http 확장자를 갖는 파일을 생성하면 VScode에서 서버로 바로 request를 보낼 수 있습니다.

GET http://localhost:3000/api/members

###
POST http://localhost:3000/api/members
Content-Type: application/json

{
  "id": 11,
  "name": "Zake",
  "team": "Engineering",
  "position": "Android Developer",
  "emailAddress": "zake@google.com",
  "phoneNumber": "010-xxxx-xxxx",
  "admissionDate": "2023/05/06",
  "birthday": "1995/09/27",
  "profileImage": "profile11.png"
}

app 객체에 Middleware 함수를 실행하지 않았기 때문에 아직은 POST request를 보낼 수 없습니다.


8. 새 직원 정보 추가하기

Middleware

미들웨어(Middleware)는 Express.js에서 요청(Request)과 응답(Response) 사이에 실행되는 함수입니다. 미들웨어 함수는 HTTP 요청과 응답을 조작하여 웹 애플리케이션을 확장하고 기능을 추가할 수 있습니다.

미들웨어는 app.use() 함수를 이용하여 등록하며, 다음과 같은 형태를 가집니다.

app.use(function(req, res, next) {
  // 미들웨어 함수 코드
});

미들웨어 함수는 파라미터로 req(요청 객체), res(응답 객체), next(다음 미들웨어 함수)를 전달받습니다. 이 매개변수를 이용하여 다음과 같은 작업을 수행할 수 있습니다.

  • 요청 객체(req)에서 필요한 정보를 추출하여 처리
  • 응답 객체(res)에 필요한 정보를 추가하여 처리
  • 다음 미들웨어 함수를 호출하여 연결된 미들웨어를 순차적으로 실행
  • 다음 미들웨어 함수를 호출하지 않고, 직접 응답을 반환할 수도 있음

미들웨어는 여러 개를 연결하여 사용할 수 있습니다. 이 경우, 다음 미들웨어 함수를 호출하기 위해서는 next() 함수를 호출해야 합니다.

새 직원 정보 추가하기

app.use(express.json());

app.post("/api/members", (req, res) => {
  const newMember = req.body;
  members.push(newMember);
  res.send(newMember);
});

express.json() Express.js에서 제공하는 내장 미들웨어 함수 중 하나로, JSON 형식의 요청(Request) 본문을 파싱하여 req.body 객체에 저장하는 역할을 수행합니다.


9. 미들웨어

express는 그 코드가 GitHub라고 하는 곳에 공개된 오픈 소스 프로젝트입니다. expressjs/express: Fast, unopinionated, minimalist web framework for node.

이 중에서 express를 import할 때 참조되는 파일인 index.js를 보면

index.js 파일은 lib라는 디렉토리 안에 있는 express라는 파일에서 import하는 것들을 그대로 export합니다.
lib/express.js 파일을 열어서 그 상단 부분을 보면

createApplication이라는 함수를 공개하고, 함수가 실행될 때 app이라는 함수를 리턴합니다.

import express from "express";
const app = express();

여기서 app 객체는 app이라는 express 내장 함수였던 것입니다. 스크롤을 조금 더 내려보면

이런 식으로 createApplication 함수에 json이라는 프로퍼티를 설정하고 있습니다.

json 프로퍼티에 설정된 bodyParser는 express 패키지가 의존하고 있는 다른 패키지에 존재하는 객체입니다. express 패키지의 package.json 파일을 살펴보면

이 패키지가 의존하고 있는 패키지 중에 body-parser라는 패키지가 있습니다.
body-parser - npm

body-parser 패키지는 리퀘스트가 라우트 핸들러에 의해 처리되기 전에 리퀘스트의 바디에 관한 전처리(preprocessing) 기능을 갖고 있는 패키지입니다. body-parser에는 JSON 형식의 데이터를 파싱하는 기능도 있음을 알 수 있습니다.body-parser 패키지 레포지토리를 보면

일단 exports가 가리키는 객체가 공개되고 있고, 그 아래 보이는, Object.defineProperty로 시작되는 코드는 exports 객체에 json이라는 프로퍼티를 설정하는 코드입니다. 지금 defineProperty 메서드의 세 번째 아규먼트로 들어온 객체는 json이라는 프로퍼티 자체가 가질 속성들으로, 여기서 get이라는 부분은 나중에 이 프로퍼티에 접근할 때 실행될 getter 함수를 의미합니다.

₩express.json₩은 bodyParser.json을 의미하고 그것은 지금 보이는 createParserGetter('json')의 리턴값을 가리킵니다.

createParserGetter 함수는 get이라는 함수를 리턴합니다. 이 get이라는 함수는 다시 loadParser 함수의 리턴 결과를 리턴합니다. loadParser 함수도 그 밑에 있습니다. loadParser에 문자열 'json'을 넣고 실행하면 또다른 파일에서 가져온 parserparsers 객체에 설정하고 parser를 리턴합니다. ./lib/types/json 파일은 json이라는 함수를 리턴하고 있습니다. 그리고 json 함수는 결국 jsonParser라는 함수를 리턴합니다.

결론적으로 express.json()은 지금 보이는 jsonParser라고 하는 함수를 리턴하는 부분이었던 겁니다.

app.use(express.json());

위 코드는

app.use(jsonParser);

위 코드와 같습니다. 이 jsonParser가 바로 미들웨어입니다.

body-parser/json.js at master · expressjs/body-parser · GitHub 참조


10. npm start와 npm run dev

// package.json
{
  // ...
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  },
  // ...
}

package.json의 "scripts"를 이렇게 설정하면 콘솔에서 npm start로 "node app.js"를 실행할 수 있고, npm run dev로 "nodemon app.js"를 실행할 수 있습니다.


11. --save-dev 옵션의 의미

npm install nodemon --save-dev 

--save-dev 옵션은 해당 모듈을 devDependencies에 추가하라는 옵션으로, 이 옵션을 함께 사용하면 설치된 모듈은 프로젝트를 개발할 때만 사용되는 모듈로 등록됩니다. 이 옵션을 사용하지 않으면 default로 --save-prod 옵션이 적용됩니다.

npm install --production를 실행해서 devDependencies 필드에 있던 패키지들은 제외하고, dependencies 필드에 있던 패키지들만 node_modules 디렉토리에 재설치할 수 있습니다.


12. 기존 직원 정보 수정하기

// app.js
import express from "express";
import members from "./members.js";

const app = express();

app.use(express.json());

// ...

app.put("/api/members/:id", (req, res) => {
  const { id } = req.params;
  const newInfo = req.body;
  const member = members.find((m) => m.id === Number(id));
  if (member) {
    Object.keys(newInfo).forEach((prop) => {
      member[prop] = newInfo[prop];
    });
    res.send(member);
  } else {
    res.status(404).send({ message: "There is no member with the id!" });
  }
});

app.listen(3000, () => {
  console.log("Server is listening...");
});

Object.keys() 메소드를 사용하여 key 값이 포함된 newIfno 객체의 모든 배열을 가져옵니다.


13. 객체 프로퍼티 순회하기

Object Keys

Object.keys()는 자바스크립트에서 사용되는 내장 함수 중 하나로, 객체의 속성 이름이 포함된 배열을 반환합니다.

const newInfo = {
  "id": 11,
  "name": "William",
  "team": "Engineering",
  "position": "Android Developer",
  "emailAddress": "zake@google.com",
  "phoneNumber": "010-xxxx-xxxx",
  "admissionDate": "2021/06/12",
  "birthday": "1995/09/27",
  "profileImage": "profile11.png"
}

console.log(Object.keys(newInfo)); // !

Object.entries

Object.entries()는 자바스크립트에서 사용되는 내장 함수 중 하나로, 객체의 속성과 값이 쌍으로 묶인 배열을 반환합니다. 이 배열은 2차원 배열 형태로, 각 요소는 [key, value]의 형태로 구성됩니다.

const newInfo = {
  id: 11,
  name: 'William',
  team: 'Engineering',
  position: 'Android Developer',
  emailAddress: 'zake@google.com',
  phoneNumber: '010-xxxx-xxxx',
  admissionDate: '2021/06/12',
  birthday: '1995/09/27',
  profileImage: 'profile11.png',
};

console.log(Object.entries(newInfo)); // !

프로퍼티의 이름 뿐만 아니라 프로퍼티의 값도 바로 동시에 가져오는 것이 가능합니다.

const newInfo = {
  id: 11,
  name: 'William',
  team: 'Engineering',
  position: 'Android Developer',
  emailAddress: 'zake@google.com',
  phoneNumber: '010-xxxx-xxxx',
  admissionDate: '2021/06/12',
  birthday: '1995/09/27',
  profileImage: 'profile11.png',
};

Object.entries(newInfo).forEach((pair) => {
  console.log(`Key: ${pair[0]} => Value: ${pair[1]}`);
});

for ... in 구문

const newInfo = {
  id: 11,
  name: 'William',
  team: 'Engineering',
  position: 'Android Developer',
  emailAddress: 'zake@google.com',
  phoneNumber: '010-xxxx-xxxx',
  admissionDate: '2021/06/12',
  birthday: '1995/09/27',
  profileImage: 'profile11.png',
};

for (const property in newInfo) {
  console.log(`Key: ${property} => Value: ${newInfo[property]}`);
}


14. 기존 직원 정보 삭제하기

app.delete("/api/members/:id", (req, res) => {
  const { id } = req.params;
  const membersCount = members.length;
  const index = members.findIndex((member) => member.id === Number(id));
  if (index !== -1) {
    members.splice(index, 1);
  }
  if (members.length < membersCount) {
    res.send({ message: "Deleted" });
  } else {
    res.status(404).send({ message: "There is no momeber with the id!" });
  }
});

app.listen(3000, () => {
  console.log("Server is listening...");
});

members가 import한 constant라서 members 변수에 재할당하지 않고 splice 메서드로 삭제했다.


Reference

profile
job's done

0개의 댓글