[Node.js] Express로 서버 만들기

MINEW·2022년 11월 19일
0

Express

  1. Express 란?
    - 서버 프로그램을 만드는 Node.js의 대표적인 프레임워크입니다.
    - 프론트엔드에서 바닐라 자바스크립트 보다 리액트 등의 라이브러리를 통해 프로젝트를 구성하는 것 처럼, 백엔드에서도 Node.js 보다 Express 모듈을 통해 프로젝트를 구성하는 것이 표준입니다.
    - 설치 방법: npm i express

  2. Express 사용 이유
    - 서버 프로그램에 필요한 최소한의 기능만 제공하지만, 대신 자유롭게 구현이 가능합니다. (자유도가 높은 프레임워크)
    - 코드의 양을 줄여주기 때문에, 가독성을 높여주고 유지보수를 편하게 만들어 줍니다.

Web 서버 만들기 (+ 라우팅)

const express = require('express'); // 1번) express 모듈을 로드하고
const app = express(); // 2번) express 모듈로 객체를 만든다 (express 모듈로 만든 객체는, 보통 app 이라는 이름을 붙인다)
const port = 3000;

const users = ['Tom', 'Andy', 'Jessica', 'Paul'];

// 3번) app이라는 객체의 get 메서드를 사용. 1번째 인자에는 path, 2번째 인자에는 path에 대한 요청이 들어올때 실행될 콜백함수.
app.get('/', (request, response) => {
  response.end('<h1>Welcome!</h1>');
});

app.get('/users', (request, response) => {
  response.end(`<h1>${users}</h1>`);
});

// 4번) 동적 라우팅 (React 등 프론트엔드에서 사용했던 것과 비슷하다!)
app.get('/users/:id', (request, response) => {
  console.log(request.params); // { id: '1' }
  console.log(request.params.id); // 1 (typeof는 string)
  const userName = users[request.params.id - 1];
    
  response.end(`<h1>${userName}</h1>`);
});

// 5번) * 는 모든 페이지라는 의미로, 무조건 맨 마지막에 해줘야한다! 위에서 사용하면, 정적으로 페이지를 지정해준것과 관계없이
// 7번) 참고로 리액트에서도 * 를 맨 마지막에 넣어줘야한다.
app.get('*', (request, response) => {
  response.end('<h1>Page Not Available</h1>'); // 6번) 항상 이 문구만 나온다!
});

app.listen(port);

API 서버 만들기 (GET, POST, PUT, DELETE)

const express = require('express');
const app = express();
const port = 3000;

const db = require('./models'); // 1번) models 디렉토리 안에 있던 index.js 파일에서 db 객체를 가져온다. (index.js는 생략가능)
const { Member } = db; // 2번) db 객체에서 Member 모델을 꺼낸다.

// 3번) DB 데이터 가져오기
sequelize
  .sync({ force: false })
  .then(() => {
    console.log('성공 ~');
  })
  .catch((error) => {
    console.log('실패 ~');
    throw error;
  });

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

// 4번) 모델이 갖고 있는 대부분의 메서드들은, promise를 리턴하는 비동기 실행함수 이기 때문에, async await 을 사용해줘야 한다.
app.get('/api/members', async (req, res) => {
  const { team } = req.query; // ex) { team: 'sales' } or { team: 'sales', id: '40' } ...
  if (team) {
    /* 
      5번) where 프로퍼티는 조건을 설정할 때 사용하며, 특정 컬럼과 값을 적어주면,
      특정 컬럼에 특정 값을 가진 row만 조회할 수 있다.
    */
    // 6번) order 프로퍼티는 데이터를 정렬하는 순서를 지정할 수 있다. (공식문서를 보면 다양한 방법들이 나와있으니 참고)
    const teamMembers = await Member.findAll({ where: { team: team }, order: [['admissionDate', 'DESC']] });
    res.send(teamMembers);
  } else {
    // 3번) findAll 메서드: Members 테이블에 있는 모든 row를 조회해서 가져오는 함수.
    const members = await Member.findAll({ order: [['admissionDate', 'DESC']] });
    res.send(members); // 참고: res 객체의 send 메서드는, 내부적으로 모델 객체의 toJSON 메서드를 호출해서, 그 결과를 res에 담는다. 그래서 res에서 깔끔하게 원하는 데이터만 볼 수 있는 것이다.
  }
});

app.get('/api/members/:id', async (req, res) => {
  const { id } = req.params; // 8번) 이때, id는 string이고, Members에 있는 id 컬럼의 값들은 number인데도,

  /* 
    7번) findOne 메서드: 테이블의 특정 row 하나만을 조회해서 가져오는 함수. (있는 경우에는 해당 요소를, 없는 경우에는 null 반환)
    단, findOne 메서드는 조건에 해당하는 row가 여러개 존재한다고 해도, 그 중에서 가장 첫 번째 row 하나만 조회해서 가져온다. (find 메서드처럼)
    따라서, 조건에 해당하는 여러개의 row(배열 요소를) 조회해서 가져와야 할 때는, 5번처럼 findAll 메서드를 사용해야한다.
  */
  const member = await Member.findOne({ where: { id: id } }); // 9번) Number(id)로 타입을 바꾸지 않아도 된다.
  if (member) res.send(member);
  else res.status(404).send({ message: 'There is no such member' });  
});

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

  // 10번) build 메서드: 하나의 Member 모델 객체를 '생성'하고 리턴한다. 이 모델 객체는 테이블에서 하나의 row가 될 존재이다.
  const member = Member.build(newMember); // 참고: build 메서드는 await을 사용 X
  // member.name = 'Mike'; // name 프로퍼티 변경 // 12번) 추가로, 들어오는 객체에서 특정 프로퍼티의 값을 바꿔서, database에 넘겨줄 수도 있다.
  // 11번) save 메서드: 위에서 생성한 Member 모델 객체의 save 메서드를 호출하면, 실제로 테이블에 위 Member 모델 객체의 내용대로 새로운 row가 추가된다.
  // 추가로, save 메서드는, 새로운 요소를 추가할 때 저장하는 역할뿐만아니라, 기존 요소를 수정할 때 저장하는 역할도 한다.
  await member.save(); // 참고: save 메서드는 await을 사용 O
  
  // 13번) create 메서드: create 메서드를 사용하면, 10번 + 11번 을 한 번에 해결할 수 있다. 
  // 특정 프로퍼티의 값을 바꿀 필요가 없다면, create 메서드를 사용하는 것도 좋은 방법. (취향차이)
  const member = await Member.create(newMember);

  res.send(member);
});

app.put('/api/members/:id', async (req, res) => {
  const { id } = req.params;
  const newInfo = req.body;

  /* 
    14번) update 메서드: 1번째 인자는 새로운 정보, 2번째 인자는 수정할 row를 특정하기 위한 조건 객체.
    update 메서드는 row 중, 수정하고 싶은 특정 컬럼과 값만 보내주면, 받지 않은 나머지 컬럼은 기존 값을 유지합니다. (patch 인듯)
  */
  // 15번) result는 id가 있는 경우에는 [ 갱신된 row의 수 ] 으로, 없는 경우에는 [ 0 ] 값을 리턴한다.
  const result = await Member.update(newInfo, { where: { id: id } });
  if (result[0]) { // 16번) 따라서, result[0]의 값으로 id 존재여부를 확인해야한다.
    const member = await Member.findOne({ where: { id: id } });
    res.send(member);
  } else {
    res.status(404).send({ message: 'There is no member with the id!' });
  }
});

app.delete('/api/members/:id', async (req, res) => {
  const { id } = req.params;

  // 17번) destroy 메서드: 해당 조건을 가진 row만 삭제된다. 실제로 삭제된 row의 갯수가 리턴된다.
  const deleteItemCount = await Member.destroy({ where: { id: id } });
  if (deleteItemCount) {
    res.send({ message: `${deleteItemCount} row(s) deleted` });
  } else {
    res.status(404).send({ message: 'There is no member with the id!' });
  }
});

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

profile
JS, TS, React, Vue, Node.js, Express, SQL 공부한 내용을 기록하는 장소입니다

0개의 댓글