본문 바로가기
카테고리 없음

[새싹x코딩온] 웹 개발자 부트캠프 과정 4주차 회고 | module & http & express

by 새파란레몬 2023. 8. 10.

모듈(Module)

특정 기능을 하는 함수나 변수들의 집합이다. 재사용 가능한 코드 조각들의 모음이라고도 볼 수 있다. 

 

이 모듈은 4 가지 특성을 가진다. 

1. 코드 추상화(abstraction): 구체적인 것을 감추고 전체적인 (핵심적인) 특성을 드러내는 것

2. 코드 캡슐화(encapsulation): 내부의 정보를 최소화해서 외부에 보여주는 것. 보안 측면에서 의의를 지닌다. 

3. 코드 재사용: 많이 쓰는 코드들을 정리해놓아 재사용이 용이하다.

4. 의존성관리 : 모듈과 모듈간의 관리가 가능하다. 

 


 

모듈을 사용하는 것은 2 가지 단계로 나눌 수 있다. 

 

< 1단계: 모듈에 코드를 넣는 과정 >

//math 2 module
//자주 사용할 함수와 변수를 정의한 파일

const add = (a, b) => a + b;
const PI = 3.141592;
const E = 2.718;

const sub = (a, b) => a - b;
const multiple = (a, b) => a * b;
const divide = (a, b) => a / b;

//case1. 객체를 감싸서 내보내기(encapsulation? )
//본래 하나 밖에 내보내지 못함
// module.exports = {
//   add: add,
//   sub: sub,
//   multiple: multiple,
//   divide: divide,
//   PI: PI,
//   E: E,
// };

module.exports = {
  add,
  sub,
  multiple,
  divide,
  PI,
  E,
};

//case2. 하나씩 내보내기
// module.exports.add = add;
// module.exports.PI = PI;
// module.exports.E = E;

// case2를 더 간단히!
// exports.add = add;
// exports.PI = PI;
// exports.E = E;

자주 사용할 함수와 변수를 정의한 js 파일이 있다. 위의 코드에서는 add, sub, multiple, divide의 함수와 PI, E 변수가 각각 정의 되어 있다. 이 함수와 변수들을 다른 js에서 사용하려면 export하는 과정을 거쳐야 한다. 본래는 case 2와 같이 하나씩 내보낸다. module.exports.add = add; 와 같은 명령어를 사용한다. 하지만 이는 번거롭기 때문에 묶어서 내보내는 case 1과 같은 방식도 존재한다. module.exports = {}; 의 괄호 안에 key: value 형식으로 적어줄 수 있는데, key 와 value 값이 일치한다면 add, sub, multiple과 같은 방식으로 써도 무방하다. 

 

< 2단계: 모듈에서 코드를 불러오는 과정 > 

//math2 모듈을 불러와 사용

//math 2 모듈 불러오기
const math2 = require('./math2');
//math2라는 객체
// const math2 = {
//     add: add,
//     sub: sub,
//     multiple: multiple,
//     divide: divide,
//     PI: PI,
//     E: E,
//   };

const { add, sub, multiple, divide } = require('./math2');

console.log(math2.add(5, 7));
console.log(math2.sub(5, 7));
console.log(math2.multiple(5, 7));
console.log(math2.divide(5, 7));
 
console.log(add(5, 8));

코드 출력 결과(node 파일명.js를 입력)

모듈은 const math2 = require('./math2');에서 알 수 있듯이 가져온 정보를 객체에 저장하는 방식으로 이루어진다. require라는 명령어를 사용하는데 괄호 안에는 파일명을 적어준다. 해당 명령어는 주석처리한 객체를 가져온 것과 같은 효과를 가진다. js와 같은 확장자는 생략한다. 아니면 객체 구조 분해를 이용해 const {add, sub, multiple, divide} = require('./math2');와 같이 적어줄 수도 있다.

 

객체 구조 분해를 사용하지 않으면 객체를 사용하는 것과 같이 math2.add(5,7);과 같이 사용해 주어야 한다. 하지만 객체 구조 분해를 이용하면 add(5,8);처럼 더 간단하게 사용할 수 있다. 

 

 


 

객체 구조 분해에 대하여 더 자세히 살펴보자면 아래와 같다. 객체 구조 분해는 다른 말로 구조 분해 할당이라고도 할 수 있는데 구조를 분해해서 할당을 하겠다는 말과 같다. 

 
// 객체 구조분해
const cookie = {
  choco: '초코맛 쿠키',
  vanilla: '바닐라맛 쿠키',
  orange: '오렌지맛 쿠키',
};

console.log(cookie.choco);
console.log(cookie.vanilla);
console.log(cookie.orange);

// 객체를 구조분해 해보쟈!!
const { choco, orange, vanilla } = cookie;
// 객체는 순서를 저장하는 구조가 아니기에 순서 상관 없음
 
console.log(choco);
console.log(vanilla);
console.log(orange);

위와 같은 코드에서 cookie라는 객체에 3개의 데이터가 저장되어 있다. 본래 사용하려면 cookie.choco, cookie.vanilla와 같이 사용해야 한다. 하지만 구조분해를 하면 앞의 cookie.을 입력할 필요없이 사용할 수 있다. 이 과정은 반드시 이름이 일치해야 가능하다. 하지만 아래 코드와 같이 순서는 관계 없다. (객체가 순서를 저장하지 않기 때문이다.)

 

위의 코드의 출력 결과는 아래와 같다. 

 


 

JavaScript에서 이런 모듈을 다루는 방식은 대표적으로 4 가지가 있다. 하지만 AMD와 UMD는 현재 잘 사용하지 않는다. 

 

1. CommonJS 모듈

자바스크립트 표준 모듈 방식은 아니지만 노드(Node.js)에서 가장 많이 쓰이는 모듈 방식이다. 표준 방식보다 먼저 등장하여 가장 많이 쓰인다. module.exports와 require 의 명령어로 모듈을 관리한다. node에서 더 많이 쓰이는 방식이다

 

2. AMD(Asynchronous Module Definition) x

3. UMD(Universal Module Definition) x

 

4. ES2015 (ES6)

 

표준방식으로 import(불러오기), export(내보내기)의 키워드로 모듈을 관리한다. 리액트에서 자주 쓰이는 방식이다. 

 

Node.js 를 쓰는 회사를 지원한다면, 면접에서 1번과 4번을 구분해서 말할 줄 알아야 한다고 한다.


NPM (node package manager)

npm은 노드패키지를 관리해주는 틀로서 라이브러리 검색이 가능하다. 이름에서 알 수 있듯이 노드 생태계에서만 유효한 패키지이다. 

 

npm init 라는 명령어로 프로젝트를 시작하고, 문답식으로 package.json에 입력될 수 있다.(npm init --yes 로 기본 값으로 생성할 수도 있다.) json은 확장자의 한 종류이다. 프로젝트에서 사용할 패키지는 npm install + 패키지 이름으로 설치가 가능하다. 

 

http 모듈로 웹 서버 생성하기

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Node http 모듈</title>
  </head>
  <body>
    <h1>안뇽!</h1>
    <p>
      nodejs에서 http 모듈을 이용해 클라이언트의 요청에 대해 응답을 했습니다.
    </p>
  </body>
</html>

위의 index.html을 서버로 실행한 결과

// http 모듈로 웹 서버 생성

// 클라이언트가 Localhost:PORT 요청을 날렸으나, 서버가 응답하고 있는 내용이 없음.
const http = require('http');
const fs = require('fs'); //파일 관련 내장 모듈
const server = http.createServer(function (request, response) {
  // req, res로 줄여서 현업에서 자주 사용

  try {
    // html 파일 불러오기
    const data = fs.readFileSync('./index2.html');
    response.writeHead(200, { 'content-type': 'text/html; charset=utf8' });
    response.write(data);
    response.end();
  } catch (error) {
 
    // 퀴즈: 404.html 만들어서 해당 html을 응답으로 보내도록 코드 수정
    const err = fs.readFileSync('./pr-error.html');

    console.error(error);
    response.writeHead(404, { 'content-type': 'text/html; charset=utf8' });
    response.write(err);
    response.end();
  }
});
 
 
const PORT = 8000;
// 서버 실행

// request 이벤트: 클라이언트 요청
// connection 이벤트: 클라이언트가 접속(클라이언트와 서버가 연결되었을 때) 발생
server.on('request', function (request, response) {
  console.log('request 이벤트 발생!!');
});
server.on('connection', function (request, response) {
  console.log('connection 이벤트 발생!!');
});

server.listen(PORT, function () {
  console.log(`server listening on ${PORT} port`);
});

server.on과 server.listen의 console.log 결과

HTTP Request / Respose 양식

: 웹에서 클라이언트와 서버가 데이터를 주고 받을 때의 양식이다.

 

위의 코드와 같은 request/response body 형식을 통해 요청 응답에 대한 내용을 작성할 수 있다.

 

또한 try-catch 구문으로 "에러 헨들링"을 할 수 있다. 해당 구문의 형식은 아래와 같다.

 

try{
시도해야하는 일
} catch (error) { 
 에러가 발생했을 때 처리할 일
}

여기서 error는 현재 발생하고 있는 에러 정보를 담는 객체로 상황마다 담기는 객체가 다 다르다. 

 

위의 try-catch 구문은 if else와 같은 방식으로 작동한다. 먼저 try 블럭을 실행하고 에러가 발생하지 않으면 유지되고 catch 블럭은 거치지 않는다. 하지만 try 블럭에서 에러가 발생하면 catch 블럭으로 들어가 현재 발생한 에러 정보가 error 변수에 담긴다. 

 

헤더(header)/본문(body)

request/response header로서 요청/응답에 대한 대표 정보가 나타난다. 응답 상태 코드(숫자코드)를 기입하여 보내다. 숫자코드에 따른 상태에 대한 정보는 아래와 같다. 

숫자 코드 (출처: 새싹캠퍼스 x 코딩온)

 


express

 

더 간단히 사용하기 위한 express가 존재한다. npm install express로 설치할 수 있다. (또 이런 것 중 Nest.js라는 것도 존재한다.)

const express = require('express'); //express 호출
const app = express(); //app에 express저장
const PORT = 8080;

//express 템플릿 엔진 종류 등록
app.set('view engine', 'ejs'); //express에서 사용할 탬플릿 엔진 종류(ejs) 등록
app.set('views', './views'); //템플릿 엔진 파일을 저장할 위치 등록

// app.get(경로, 해당 경로로 들어왔을 때 실행할 함수)
// '/' : 서버주소: 포트번호/ (localhost:8080/)
app.get('/', function (request, response) {
  // response.send(x): x를 클라이언트한테 응답
  // response.send('<h1>Hello! Express!!</h1>');

  // response.render(ejs_filename): ejs file이름을 찾아서 응답
  response.render('index'); //index이름을 갖는 파일을 응답
});

// '/sesac' 경로로 들어왔을 때 "새싹 영등포캠퍼스 5기 수업중" 메세지 보이기
app.get('/sesac', function (request, response) {
  response.send('<h1>새싹 영등포캠퍼스 5시 수업중~</h1>');
});

// 퀴즈
// 1. /Login 경로로 요청이 들어오면 로그인 페이지(ejs)를 응답
app.get('/login', function (request, response) {
  response.render('login'); //login이름을 갖는 파일을 응답
});

// 2. /register 경로로 요청이 들어오면 회원가입 페이지(ejs)를 응답
app.get('/register', function (request, response) {
  response.render('register'); //register이름을 갖는 파일을 응답
});

//서버가 실행할 PORT 지정하고, 실행했을 때 콘솔로그를 찍음
app.listen(PORT, function () {
  console.log(`server listening on ${PORT} port`);
});

먼저 require를 통해 express를 호출하여 변수에 저장하여 준다. 포트 번호는 8080으로 지정하였다. express에서 ejs 엔진 선택후 저장할 위치까지 등록해준다. ejs 는 html형식의 파일이지만 반복되는 코드를 for 문 등으로 구현해줄 수 있기에 편리하다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>메인 페이지</title>
  </head>
  <body>
    <h1>Hello! Express!!</h1>
    <p>안녕하세요~~~~</p>
    <% for (let i=0; i<5; i++) { %>
    <div>하이</div>
    <% } %>
    <a href="./login">로그인 </a>
    <a href="./register">회원가입 </a>
  </body>
</html>

위의 코드가 구현된 index.ejs 파일의 결과 화면

render로 index이름을 갖는 파일을 응답한다고 지정해준다.  app.get을 이용하면 localhost:8080/파일이름으로 서로 다른 웹페이지로의 전환이 가능하다. (index.ejs, login.ejs, register.ejs의 각각의 파일이 존재해야한다.) 위의 코드에서는 또한 listen을 이용해 콘솔로그로 메세지를 띄워 잘 작동하고 있는지까지 확인할 수 있다.