본문 바로가기

Node.js

[코드잇] Express 기본 익히기

1. Express란?

 

- Node.js 환경에서 실행되는 서버 프로그램을 만들 때 사용하는 프레임워크 

- 서버 프로그램에 필요한 최소한의 기능만 제공하고 프로그램의 전체적인 구조는 개발자가 자유롭게 구현할 수 있음

- 자유도가 높은 프레임워크 

 

2. API 서버란?

 

- Server의 두가지 종류

 1) 웹페이지를 Response의 Body에 담아서 보내주는 서버  = Web Server 

  - 화면을 그리는데 필요한 재료를 Response의 Body에 담아서 줌

 

 2) 요청을 처리하고 처리한 결과를 Response의 Body에 담아서 보내주는 서버 = API  Server

  - 요청한 작업을 처리하고 처리한 결과를 Response의 Body에 JSON 등의 형식으로 담아서 줌

 

 

 

3. Express 사용해보기 

 

- 현재 디렉토리를 패키지로 만드는 방법 - npm init

- Express 패키지 설치 - npm install express

 

- 특정 Path에 대응하는 콜백 = 라우트 핸들러 (route handler)

- route는 서버가 각 리퀘스트의 path 부분을 보고 알맞은 작업을 수행하는것

- handler는 그 작업을 담당하는 존재를 의미  

- 첫번째 파라미터 req => request 객체로, 이 객체를 통해서 클라이언트가 보낸 리퀘스트를 다룰 수 있음

- 두번째 파라미터 res => response 객체로, 이 객체를 통해서 적절한 리스폰스를 보낼 수 있음

 

- 포트 번호 (Port Number) 

  - 서버 안에서 실행되는 여러 프로그램들 중 특정 프로그램을 식별할 수 있게 해주는 번호 

//Express 패키지에서 공개하는 것을 가져와야 함
const express = require('express');

//Express 패키지는 함수 하나를 외부에 공개해야함
const app = express();

//외부의 클라이언트가 보낸 리퀘스트의 URL Path 부분이
// /hello라면 함수가 실행
// 콜백 = 특정 조건이 만족되었을 때 실행되는 함수 
app.get('/hello', (req,res)=> {
    res.send('<h1>Hello Express</h1>');
});

//외부에서 리퀘스트가 오는것을 기다리도록 하는 메소드 
//이것까지 써줘야 프로그램이 외부의 리퀘스트를 받을 수 있음
//포트번호 3000
app.listen(3000);

- 터미널 창에서 node app.js를 입력하면 서버 프로그램 실행

- 서버 프로그램이 지금 브라우저가 실행 중인 컴퓨터와 같은 컴퓨터에서 실행되고 있기 때문에 localhost라고 

 

- 서버가 외부의 리퀘스트를 들을 준비를 마치고 나면 자동으로 실행

- 서버가 정상적으로 실행되었는지를 확인하기 위해서 listen 메소드에 이런 식의 콜백을 넣어주는 것이 좋

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

 

 

4. 직원 정보 조회하기 

 

- Cowork의 직원 정보 관리 웹 서비스에서 사용할 API 서버 제작 

- 실제 서비스에서는 직원 정보 같은 데이터를 저장하기 위해서 데이터베이스를 사용 

  - 여기에서는 직원정보를 배열로 관리

 

- send 메소드에 배열을 넣으면 배열을 JSON 문자열로 변환한 결과를 response의 Body에 담아서 보내주게 

 

members.js

module.exports = [
    {
      id: 1,
      name: 'Alex',
      team: 'engineering',
      position: 'Server Developer',
      emailAddress: 'alex@google.com',
      phoneNumber: '010-xxxx-xxxx',
      admissionDate: '2018/12/10',
      birthday: '1994/11/08',
      profileImage: 'profile1.png',
      ......
    },

app.js

const express = require('express');


const app = express();

let members = require('./members');

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


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

 

 

Route Parameter

 - path 부분에서 가변적인 값이 전달되는 부분에 사용 

 - Route Parameter의 값은 req 객체의 params라고 하는 객체의 프로퍼티로 설정

 - req.params.id로 id 값을 가져올 수 있음

 

- 직원 정보 배열에 있는 여러 객체들 중에서 해당 id 값을 가진 객체를 찾기 위해 배열의 find 메소드를 사용

- find는 그 인자로 들어온 콜백 함수가 true를 리턴하게 하는 배열의 여러 요소들 중에서 첫 번째 요소를 리턴 

- 만약 해당 id를 가진 직원 정보가 있다면 그 직원 정보를 그대로 response에 담아서 보내주도록 함 

 

app.js

...
//members/뒤에 오는 값을 id변수에 대입
//라우트 파라미터(Route Parameter)
app.get('/api/members/:id', (req,res)=> {
    //const id = req.params.id;
    const {id} = req.params;
    const member = members.find((m)=>m.id === Number(id));
    if(member) {
        res.send(member);
    }else {
        //404 : 요청한 정보가 없다는 걸 나타냄
        res.status(404).send({message:"There is no such member"});
    }
});
...

 

5. 리소스란?

 

리소스(Resource)

 - 서버에 저장돼 있는 수많은 정보 

 

 

6. 특정 팀만 조회하기 

 

 

- URL 끝에 ? 이후의 부분을 쿼리(Query)라고 함 

- 쿼리는 서버에 있는 데이터를 조회할 때 기준을 정하기 위해 사용

 

- URL에 team이라는 파라미터 값이 있는 경우에는 전체 직원 정보들 중에서 해당 팀에 속한 직원들만 추리면 됨

- filter 메소드는 배열의 각 요소를 순회하면서 인자로 들어온 콜백이 true를 리턴하는 요소들만 모아서 새로운 배열을 만듬

 

app.js

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

 

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

 

- app 객체의 get 함수가 실행되려면 request의 메소드가 GET이어야 한다는 조건이 필요 

- app 객체의 get 함수는 특정 path로 오는, GET 메소드를 가진 리퀘스트를 처리하는 라우트 핸들러를 등록함

- 기존 리소스를 수정하려는 PUT 리퀘스트, 기존 리소스를 삭제하려는 DELETE 리퀘스트도 있음

 

POST 리퀘스트 보내기 

- app 객체의 POST 함수 사용

-새로 추가하려고 하는 리소스의 내용이 해당 리퀘스트의 바디에 담겨있어서 서버에서 바디의 내용을 별도로 처리해줘야 함

 

app.js

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

 

rest client 플러그인 설치 

 -> VS Code에서 바로 리퀘스트를 보내고 리스폰스를 볼 수 있음

- 헤드 뿐만 아니라 바디도 있음

- 헤드에는 POST 메소드, 리퀘스트를 보낼 URL

- 바디에는 데이터의 타입을 나타내는 Content-Type헤더가 들어 있음

 (applicaiton/json 값은 바디에 있는 데이터 타입이 JSON이라는 뜻)

 

 

8. 새 직원 정보 추가하기 

 

- express를 사용할 때 바디가 있는 리퀘스트를 처리하려면 특정 코드를 추가해줘야함 

 

app.js

app.use(express.json());

- express.json() 메소드는 함수를 리턴 

 -> 그 함수는 서버로 온 리퀘스트의 바디에 JSON 데이터가 존재할 경우에 그것을 추출해서 리퀘스트 바디의 body 프로퍼티의 값으로 설정 

 -> 리퀘스트가 라우트 핸들러에 의해 처리되기 전에 추가적으로 필요한 전처리를 수행하는 함수를 미들웨어(middleware)라고  

 

- 리퀘스트가 들어오게 되면 middleware에 의해서 바디에 있는 JSON 데이터가 res 객체의 body 프로퍼티에 설정되고 

리퀘스트의 path와 메소드를 보고 알맞은 라우트 핸들러가 호출

 

app.use(express.json());
app.use((req, res, next) => {
  console.log(req.query);
  next();
});

- 미들웨어는 형식적으로 req, res, next 이렇게 3개의 파라미터를 가진 함수이고, 기능적으로는 라우트 핸들러 이전에 리퀘스트에 관한 처리를 해주는 함수 

- 미들웨어는 app 객체의 use 메소드로 설정할 수 있음 

- 미들웨어 안에서 원하는 처리를 모두 수행한 후에는 마지막에 next를 호출하여 리퀘스트를 그 다음 미들웨어 또는 라우트 핸들러로 넘겨줘야 함 

 

 

9. nodemon과 npm start

 

- 코드가 실행되면 자동으로 프로그램도 재실행되도록 nodemon 패키지 사용

- nodejs.18 버전부터는 nodemon 패키지를 설치하지 않아도 node --watch server.js 로 사용 가능

 

package.json

"scripts": {
    "start" : "node app.js"
  },

- npm start라고만 써도 서버를 실행할 수 있음

 

 

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

 

1) --save-dev 옵션없이 설치했을 떄 

 

- dependencies 필드에 nodemon 패키지가 보임

 

 

2) --save-dev 옵션을 주고 설치했을 때 

 

- dependencies 필드가 아니라 devDependencies 필드에 nodemon 패키지의 정보가 있다는 것을 알 수 있음 

 

 

- 프로젝트의 코드를 실행하는 경우 

 

 1. 개발/테스트 목적의 코드 실행

 - 첫 개발부터 시작해서 중간중간 코드를 제대로 작성해가고 있는 건지를 테스트하기 위한 목적에서의 코드 실행

 

 2. 실제 서비스 제공을 위한 코드 실행

 - 충분한 검증을 거진 코드를 서비스 제공 목적으로 배포해서 실행하기 위한 목적에서의 코드 실행

 

- 배포 용도로는 필요하지 않고 개발 용도로만 필요한 패키지를 설치할 때 --save-dev 옵션을 사용

- 하나의 프로젝트에서 

  - 배포 용도로 필요한 패키지들은 dependencies 필드에, 

  - 개발 용도로만 필요한 패키지들은 devDependencies 필드에, 그 정보가 기재되어야 함

 

- 프로젝트의 코드를 배포 용도로 실행하기 전 과정

 

  1. 실제 개발 중이던 프로젝트 디렉토리를 하나 더 복사 

 

  2. 새 디렉토리에서 node_modules 디렉토리 삭제

 

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

 

  4. 실제 서비스를 위해 코드 실행 (npm start 등 실행)

 

 

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

 

- 서버에 있는 기존의 직원 정보를 수정하는 PUT 리퀘스트를 처리

 

- 해당 id를 가진 객체가 있으면 새 정보의 내용대로 해당 객체의 모든 프로퍼티의 값을 수

app.js

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:'});
    }
});

 

 

12. 객체의 프로퍼티 순회하기 

 

- 자바스크립트에서 특정 객체가 갖고 있는 모든 프로퍼티를 확인할 수 있는 방법

 

1) Object.keys

 

- 특정 객체의 모든 프로퍼티를 조회 

if (member) {
    Object.keys(newInfo).forEach((prop) => {
      member[prop] = newInfo[prop];
    });
    res.send(member);

 

2) Object.entries

 

- 각 프로퍼티의 이름(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]}`);
});

 

3) for...in 구문 

 

- 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(property);
}

 

- 프로퍼티의 값도 출력하려면 

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]}`);
}

 

 

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

 

- 기존 직원 정보를 삭제하는 DELETE 리퀘스트 처리 

- DELETE 리퀘스트를 처리하는 라우트 핸들러는 app 객체의 delete 메소드로 등록

 

- 직원 정보를 삭제하려면 직원 정보 배열에서 해당 직원의 정보 객체만 삭제해주면 됨 

- 배열의 filter 메소드를 사용해 기존의 직원 정보 배열에서 삭제할 직원 정보 id값과 일치하지 않는 요소들만 추려내서 

 새로운 배열을 만들고 그 배열을 새로운 직원 정보 배열로 설정

- 삭제되었다는 문장을 메시지로 돌려 보내줌

 

app.js

app.delete('/api/members/:id',(req,res) => {
    const { id } = req.params;
    const memberCount = members.length;
    members = members.filter((member) => member.id != Number(id));
    if(members.length < membersCount) {
        res.send({message: 'Deleted'});
    } else {
        res.status(404).send({messge: 'There is no member with the id!'});
    }

});