본문 바로가기
학원에서 배운 것/node.js

KDT 5th 웹개발 입문 수업 25일차

by 쿠리의일상 2023. 3. 10.

Module

특정 기능을 수행할 수 있는 최소한의 단위

➡️ 모듈이 커지면 라이브러리가 되는 것! ➡️ 패키지 ➡️ 프레임워크

 

 

JS에서 Module 사용하기

  • 다른 사람이 만든 기능을 활용할 때 코드를 매번 붙여 넣기 힘드므로 파일로 받아서 사용하는 방식을 고안
  • CommonJS 방식 (ex. require( ))
  • ES6 방식 (ex. 리액트)

 

 

CommonJS 방식

node.js에서 사용되는 모듈 방식 중 하나

전체 모듈로써 내보내고 전체를 하나의 객체로 받아서 사용하는 방법

 

키워드 : require, exports

 

1. 기능 하나하나 객체에 담아서 빼주기 

1) module.exports = { }; 으로 객체 형식으로 모듈을 빼줄 수 있다.

2) require() 로 모듈을 불러올 수 있으며, 코드 어느 장소에서든 불러올 수 있다.

const animals = ['puppy', 'cat'];

function showAnimals() {
  animals.map((el) => {
    console.log(el);
  });
}

// 1. module.exports 로 원하는 기능들을 객체로 빼준다.
module.exports = {
  animals,
  showAnimals,
};
// 2. 사용 시에는 require(폴더위치) 로 exports 한 객체를 받아온다.
const animalModule = require('./animals');

console.log(animalModule);
animalModule.showAnimals();

/*
{ animals: [ 'puppy', 'cat' ], showAnimals: [Function: showAnimals] }
puppy
cat
*/

 

2. 각각의 기능에 직접 exports.를 붙여서 모듈을 꺼내올 수 있다.

const animals = ['puppy', 'cat'];

// module.exports 방식이 아닌, 원하는 것만 하나하나 exports. 해줄 수 있다.
exports.animals = animals;
exports.showAnimals = function showAnimals() {
  animals.map((el) => {
    console.log(el);
  });
};
// 구조 분해 문법으로 exports 해준 순서대로 각각의 변수에 넣어줄 수 있다.
const { animals, showAnimals } = require('./animals');

console.log(animals);
showAnimals();

 

3. 하나의 객체 또는 클래스에 전부 때려넣어 놓고 그 객체 자체를 내보낼 수도 있다.

const animals = {
	animals: ['puppy', 'cat'],
    showAnimals() {
    	this.animals.map((el) => console.log(el));
    },
};

// 이미 animals가 객체이므로 바로 대입 가능
module.exports = animals;
const { animals, showAnimals } = require('./animals');

console.log(animals);
showAnimals();

 

 

ES6 방식

package.json "type" : "module" 를 추가 해준다!

ES6 는 CommonJS 처럼 전체를 내보내는 방식은 존재하지 않으므로 필요한 모듈을 설정하여 원하는 것만 내보내고 받는 방식을 사용해준다.

키워드 : export <-> import from, as

모듈 내보내기

// 1. 내보내고자 하는 기능 앞에 export 를 일일히 추가해줄 수 있다.
export const animals = ['puppy', 'cat'];

export function showAnimals() {
  animals.map((el) => console.log(el));
}
const animals = ['puppy', 'cat'];

function showAnimals() {
  animals.map((el) => console.log(el));
}

// 2. 아래와 같이 한번에 객체처럼 빼줄 수 있다.
export { animals, showAnimals };

 

모듈 받아오기

기본적으로 import는 코드 최상단에서만 사용 가능하다 (require은 코드 아무 지점에서든 사용이 가능하다)

from 폴더위치를 넣어줄 때, 폴더위치에 확장자명(.js) 까지도 확실히 적어야 한다

// 구조 분해 할당과 import 로 읽어올 수 있다. 폴더 위치의 경우 from 뒤에 기재해준다.
import { animals, showAnimals } from './es6_animals.js';

console.log(animals);
showAnimals();

모듈명 as 새로운모듈명 으로 export, import 에서 모듈 이름을 바꿀 수 있다.

// as 키워드를 사용하여 export 한 명칭을 변경해줄 수 있다.
import { animals as ani, showAnimals as show } from './es6_animals.js';

console.log(ani);
show();

✚ 가져올 것들이 많다면, * as 로 전체를 받아올 수 있다.

 

export default

보통 파일 하나에 하나의 큰 기능을 커버하는 클래스 또는 객체를 만드는 방식을 선호

그래서 이러한 파일은 export default 라는 문법으로 모듈을 내보낸다. 즉, 해당 파일에는 모듈 개체가 하나만 있다고 알려주는 역할을 해준다.

그래서 export default 로 내보내진 모듈을 import 할 때는 { } 를 사용하지 않는다.

 

 

CommonJS, ES6 방식은 동시에 사용할 수 없다.

package.json 에 "type" : "module" 을 지정해주면 ES6 만, 따로 없다면 CommonJS 방식으로 모듈 설정이 된다.

일반적으로 ES6 문법의 기능이 더 많고, 필요한 모듈만 불러오는 구조를 가지므로 메모리에 효율적, 성능이 우수하다.

 

 

 

Routing 원리

const express = require('express'); //CommonJS
const router = express.Router(); //express의 Router모듈을 생성하는 것임

router.get('/', (req, res) => {
	res.render('users', { USER_ARR, userCounts : USER_ARR.length });
});

router.get('/id/:id', (req, res) => {
	const userData = USER[req.params.id];
    if(userData) {
    	res.send(userData);
    } else {
    	res.send('ID 없음');
    }
});

module.exports = router; //해당 라우터에 작성된 모든 기능들을 하나의 모듈로써 외부로 보내는 것!
const express = require('express');

const app = express();
const PORT= 4000;

// 모듈을 담은 객체를 가져와서 변수에 담은 것
const userRouter = require('./routes/users');

// http://localhost:4000/users 로 들어오는 모든 요청은 userRouter에 들어있는 코드가 처리하게 설정하는 것
app.use('/users', userRouter);

 

 

Error 핸들링

에러 상황에 대한 statusCode 전달이 필요하다.

서버 Error가 발생하면 정석적으로 err를 발생시킨 다음, err 메시지statusCode를 전달해줘야 한다.

Error 생성자를 new 로 생성하여 만든 객체를 throw 키워드로 전달해준다.

 

delet.delete('/:id', (req, res) => {
  const userIndex = USER.findIndex((user) => user.id === req.params.id);
  if (userIndex !== -1) {
    USER.splice(userIndex, 1);
    res.send('회원 정보 삭제 완료');
  } else {
    const err = new Error('ID가 존재하지 않습니다.');
    err.statusCode = 404;
    throw err;
  }
});

Error 가 발생 시 Error 메세지와 Error Stack 이 출력된다. 가장 윗 줄의 at ~ 부분이 최종적으로 Error가 발생한 장소이므로

에러를 해결하기 위해 해당 프로그램 위치를 수정해준다.

 

하지만, 해당 페이지가 프론트에 보이게 하는 것은 옳지 않으므로 에러를 핸들링 해주는 미들웨어를 만들어 준다.

전체 서버를 통솔하는 app.js에서 에러 핸들링을 해준다.

// 에러 핸들링 미들웨어
app.use((err, req, res, next) => {
  console.log(err.stack);
  res.status(err.statusCode);
  res.send(err.message + `<br/><a href='/'>홈으로</a>`);
});

err.stack 은 위 Error at에서 보인 내용을 담고 있고, err.statusCode 에선 Error 생성자에서 지정해준 statusCode 를,

Error 생성자에서 작성해준 메세지는 err.message 에 담긴다.

 

 

Form으로 데이터 전송하기

  <form action="/users/add" method="POST">
    <div>
      <label>아이디</label>
      <input type="text" name="id" />
    </div>
    <div>
      <label>이름</label>
      <input type="text" name="name" />
    </div>
    <div>
      <label>이메일</label>
      <input type="text" name="email" />
    </div>
    <button type="submit">유저 등록</button>
  </form>

하지만 폼으로 보낸 데이터는 query로 들어오지 않아서 다른 방식으로 받아야 한다.

데이터를 body에 넣은 다음 인코딩처리까지 해줘야하는데,

이때 body-parser 라는 모듈을 사용하여 폼에서 전송된 정보를 req.body에 담아서 객체로 전달해주는 역할을 해준다.

npm i -S body-parser 로 설치

 

메인 app.js 에서

const bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false });

해당 모듈을 require() 해주고 모듈을 사용한다고 app.use() 로 bodyParser.json() 과 bodyParser.urlencoded() 해준다.

다만 이제는 많이 써서 express 4.16 이상에서 기본 기능으로 추가가 되었다. (실무때 옛날 버전을 쓸 가능성 때문에)

 

 

// 회원 추가 /add?쿼리문
router.post('/add', (req, res) => {
  // 쿼리로 들어왔을 때
  if (Object.keys(req.query).length >= 1) {
    if (req.query.id || req.query.name || req.query.email) {
      const newUser = {
        id: req.query.id,
        name: req.query.name,
        email: req.query.email,
      };
      USER.push(newUser);

      res.redirect('/users');
    } else {
      const err = new Error('입력이 잘못 되었씁니다.');
      err.statusCode = 400;
      throw err;
    }
    // req.body 로 들어왔을 때
  } else if (req.body) {
    if (req.body.id || req.body.name || req.body.email) {
      const newUser = {
        id: req.body.id,
        name: req.body.name,
        email: req.body.email,
      };
      USER.push(newUser);

      res.redirect('/users');
    } else {
      const err = new Error('입력이 잘못 되었씁니다.');
      err.statusCode = 400;
      throw err;
    }
  }
});

회원 추가 시, 기본적으로 주소로 받아서 query 로 데이터를 받아주었지만

폼 태그를 사용하여 회원 추가를 해주려면, 주소가 아니므로 req.body 를  사용해줘야 한다.

그래서 조건문에 req.body로 받아온 것을 쿼리와 동일하게 id, name, email 등에 접근해준다.

 

추가로 회원 등록이 되면 회원 목록을 보여줄 필요가 있으므로 res.redirect(보여줄화면url) 으로 처리해줄 수 있다.

 

 

Form 은 데이터를 서버로 보내기 위한 태그로

url 주소를 받기 위한 action 속성과 요청 메서드를 받기 위한 method 속성을 가지고 있다.

 

form 태그 없이 백엔드로 데이터를 보내는 방법으로는

  1. XMLHttpRequest -> 거의 사용되지 않음
  2. JQuery -> 편리한 DOM 기능, 데이터 통신 등 다양한 분야에서 사용되지만 제이쿼리의 기능이 JS에 기본 내장 되어 사용이 줄어드는 추세, $ 사용
  3. Fetch() -> 브라우저(프론트)에서 SSR(서버 사이드 통신)을 위하여 ES6에 추가된 기능

등이 있다.

 

Fetch

기존 XMLHttpRequest 의 문제점을 개선하고 Promise를 기본으로 내장하여 서버 통신 코드를 백엔드 코드와 비슷하게 짤 수 있다.

  // 유저 정보 삭제하는 함수
  function deleteUser(id) {
    fetch(`http://localhost:4000/users/delete/${id}`, {
      method: 'delete',
      headers: {
        'Content-type': 'application/json',
      }
    }).then((res) => {
      console.log(res);
      location.reload();
    }).catch((err) => {
      console.log(err);
    });
  }

fetch(서버 풀 주소, 옵션객체)

옵션객체로는 요청 method와 headers(json 변경) 를 적어줘야 한다.

만약 데이터 전달이 필요하면 body에 담아서 전달해준다.

fetch() 함수는 promise 이므로 then 과 catch 로 컨트롤해준다.

 

location.reload() 는 프론트에서 해당 페이지를 새로고침할 때 사용한다.