Day 14 - Express.js 구조 이해, Generator, 화살표 함수

이유승·2024년 11월 18일
0

* 프로그래머스, 타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js) 5기 강의 수강 내용을 정리하는 포스팅.

* 원활한 내용 이해를 위해 수업에서 제시된 자료 이외에, 개인적으로 조사한 자료 등을 덧붙이고 있음.

1. Express.js 구조 이해

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

app.get('/', (req, res) => {
  res.send('Hello World!');
});

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

const express = require('express');

  • express 모듈을 가져온다. Express.js를 사용할 준비하는 코드
    - Node.js에서 모듈을 불러오는 방식인 require()를 사용.
    • express는 웹 서버를 쉽게 구축할 수 있도록 도와주는 경량 프레임워크.

const app = express();

  • express()를 호출하여 Express 애플리케이션 객체를 생성. 이 객체를 통해 라우팅을 정의하거나 서버 동작 방식을 설정할 수 있다.
    - app은 서버의 핵심 객체로, 요청 라우팅, 미들웨어 설정, 서버 구성을 관리하는 역할을 수행.

app.get('/', (req, res) => { res.send('Hello World!'); });

  • GET 요청을 처리하는 라우트를 정의. 클라이언트가 루트 경로(/)로 GET 요청을 보내면 "Hello World!"라는 응답을 받는다.
    - app.get()은 HTTP GET 요청에 응답하는 핸들러를 등록하는 메서드.
    - '/': 요청 URL의 경로를 지정합니다. 여기서는 루트 경로(/)를 의미.
    - (req, res) => { ... }: 요청(req)과 응답(res) 객체를 처리하는 콜백 함수.
    - req: 클라이언트의 요청 정보를 담고 있는 객체.
    - res: 서버가 클라이언트에게 응답을 보내는 데 사용하는 객체.
    - res.send('Hello World!');: 클라이언트에게 문자열 'Hello World!'를 응답으로 전송.

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

  • 서버를 지정된 포트에서 실행. 서버가 3000번 포트에서 요청을 받을 준비가 되었음을 나타낸다.
    - 3000: 서버가 요청을 수신할 포트 번호.
    - () => { console.log(...) }: 서버가 정상적으로 실행된 후 호출되는 콜백 함수.



Express.js 애플리케이션 생성기.

npm install -g express-generator

  • Express.js 애플리케이션은 어떻게 생성하는가?

  • 공식문서 상의 순서대로, 서버 경로상에 app.js 파일을 생성하고 위에 예시로 작성한 코드를 붙여넣기해주면 기본 구조 상태로 생성할 수 있다.

  • 혹은 Express.js 애플리케이션 생성기를 사용해볼 수 있다.

  • 템플릿을 통해 Express 프로젝트의 디렉터리와 파일 구조를 자동으로 생성하며, 개발자가 초기 설정 작업에 시간을 절약할 수 있다.

  • 템플릿 엔진, 정적 파일 경로, 미들웨어 설정 등을 포함한 기본적인 앱 구조를 제공한다.

  • npm 패키지를 통해 애플리케이션 생성기를 설치하고, express '어플리케이션이름'을 입력해주면 된다.

express '어플리케이션이름'

express myapp



어떻게 사용해야하는가?

  • Express 애플리케이션의 기본 구조를 자동으로 생성해주는 도구.

  • 초기 설정 작업 최소화 / 일관된 프로젝트 구조 제공 / 기본 기능 및 템플릿 제공 등에 대단히 유용하다.

  • 다만, 작은 프로젝트에서는 사용할 일도 없는 도구들이 너무 많이 생성된다는 문제가 발생한다.

  • 더구나 정해진 프로젝트 구조 때문에 복잡한 대규모 프로젝트에서는 기본 구조가 방해가 되는 경우도 존재.
    - 그래서 프로젝트 구조를 입맛에 맞게 조정하는 경우가 많다.

  • React, Angular, Vue.js와 같은 프론트엔드 도구와 결합이 잘 안맞는 경우도 존재. 따라서 필요한 경우에 알아서 사용하고, 기본 구조가 자기 입맛에 영 맞지 않는다 싶으면 사용하지 않거나 / 구조를 적절히 수정해서 사용하는게 좋다.

사용예시

  • myapp이라는 이름으로 express 어플리케이션을 설치했다고 가정해보자.

myapp/					# 서버 실행 관련 스크립트를 저장하는 디렉터리
├── app.js                ├─# Express 앱의 진입 파일
├── bin/
│   └── www               		└─# 서버 실행 파일, app.js 파일을 불러와 HTTP 서버를 생성하고 포트를 바인딩
├── public/               ├─# 정적 파일 (CSS, JavaScript, 이미지 등)
│   ├── images/
│   ├── javascripts/
│   └── stylesheets/
├── routes/               ├─# 라우터 파일
│   ├── index.js				└─# 기본 경로(/)에 대한 요청을 처리
│   └── users.js				└─# /users 경로에 대한 요청을 처리
├── views/                ├─# 템플릿 파일
│   ├── error.pug				└─# 오류 페이지를 렌더링하는 템플릿
│   ├── index.pug				└─# 루트 경로(/)에 대한 기본 HTML 페이지를 정의
│   └── layout.pug				└─# 애플리케이션의 기본 레이아웃 파일, 공통 레이아웃(헤더, 푸터 등)을 정의
├── package.json          ├─# 프로젝트 의존성과 스크립트 정의
└── node_modules/         ├─# 설치된 의존성 모듈



app.js 파일 분석

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

// 라우터 로드
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
// 뷰 엔진 설정
// 템플릿 파일(뷰)의 위치를 지정
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// 미들웨어 설정
// morgan 미들웨어로, 개발 모드에서 요청 정보를 로그로 출력
app.use(logger('dev'));
// JSON 형식의 요청 본문(body)을 파싱
app.use(express.json());
// URL-encoded 데이터를 파싱
app.use(express.urlencoded({ extended: false }));
// 요청의 Cookie를 파싱하여 사용할 수 있도록 해준다
app.use(cookieParser());
// public 디렉터리의 파일들을 정적 파일로 제공하는 미들웨어
app.use(express.static(path.join(__dirname, 'public')));

// 불러온 라우터를 연결
app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
// 404 에러 처리
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;



www 파일 분석

// shebang(쉐뱅), 이 파일이 Node.js 환경에서 실행되어야 함을 명시
#!/usr/bin/env node


/** 모듈 의존성 로드
 * Module dependencies.
 */

var app = require('../app');
// 디버깅 메시지를 출력하기 위한 모듈입니다. myapp:server 네임스페이스로 디버깅 메시지를 관리
var debug = require('debug')('myapp:server');
// Express 앱은 HTTP 모듈을 사용하여 서버를 생성하고 실행한다
var http = require('http');


/** 포트 설정
 * Get port from environment and store in Express.
 */

// normalizePort: 문자열로 전달된 포트를 숫자 또는 유효한 값으로 변환
// 유효하지 않은 값이 전달될 경우 false를 반환
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);


/**
 * Create HTTP server.
 */

// express.js는 결국 HTTP 기반이기 때문에 createServer를 통해 서버를 생성한다.
var server = http.createServer(app);


/**
 * Listen on provided port, on all network interfaces.
 */

// 지정된 포트에서 서버를 시작
server.listen(port);
// 서버 실행 중 발생한 에러를 처리하는 이벤트 리스너를 등록
server.on('error', onError);
// 서버가 정상적으로 시작된 경우 호출되는 이벤트 리스너를 등록
server.on('listening', onListening);


/** 포트 유효성 검사
 * Normalize a port into a number, string, or false.
 */

// 입력된 포트를 유효한 값(숫자, 파이프)으로 변환
function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    return val; // 포트가 숫자가 아니면 (named pipe), 그대로 반환
  }

  if (port >= 0) {
    return port; // 포트 번호가 양수면 그대로 반환
  }

  return false; // 유효하지 않은 포트는 false 반환
}


/** 에러 처리
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error; // listen과 관련 없는 에러는 그대로 던짐
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port // 포트가 파이프일 경우
    : 'Port ' + port; // 포트가 숫자일 경우

  // 특정 에러 유형에 따른 메시지 처리
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1); // 권한 문제
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1); // 포트 중복 사용
      break;
    default:
      throw error; // 기타 에러는 그대로 던짐
  }
}


/** 서버 시작 완료 처리
 * Event listener for HTTP server "listening" event.
 */

// 서버가 정상적으로 시작된 경우 호출
function onListening() {
  var addr = server.address();
  
  // 포트 또는 파이프 여부에 따라 메시지 생성
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}



3. 화살표 함수

  • ES6(ECMAScript 2015)에서 도입된 간결한 문법.

  • 기존의 함수 선언 방식보다 짧게 함수를 작성할 수 있다.

// 기본 함수 표현식
const add = function(a, b) {
  return a + b;
};
// 화살표 함수 표현식
const add = (a, b) => a + b;

간결한 함수!

중괄호와 return 키워드 생략 가능:

  • 함수 본문에 한 줄의 표현식만 있을 경우, 암묵적으로 결과값이 반환.
const square = x => x * x;

매개변수가 하나일 때, 괄호 생략 가능:

const greet = name => `Hello, ${name}`;

매개변수가 없을 때, 빈 괄호를 사용해야 합니다:

const sayHi = () => 'Hi!';

this의 차이

  • 상세 내용은 이전 포스팅 참조.

  • 일반 함수에서 this는, 호출한 객체를 가르킨다. 다만 호출 방식에 따라 this가 가르키는 대상이 달라진다.

  • 화살표 함수에서 this는 자신이 선언된 위치를 가르킨다.
    - 그래서 call, apply, bind로 this가 가르키는 위치를 변경할 수 없다.

arguments 객체 미지원

  • 화살표 함수는 arguments 객체를 지원하지 않고, 대신, 나머지 매개변수(rest parameter, ...args)를 사용한다.
// 일반 함수
function sum() {
  console.log(arguments); // [1, 2, 3]
}

// 화살표 함수
const sum = (...args) => {
  console.log(args); // [1, 2, 3]
};

sum(1, 2, 3);

생성자 함수 미지원, 프로토타입 메서드 정의 못함..

const Person = (name) => {
  this.name = name;
};

const john = new Person('John'); // TypeError: Person is not a constructor

//
//

const obj = {
  value: 10,
  method: () => console.log(this.value)
};

obj.method(); // undefined (this는 obj가 아닌 상위 컨텍스트를 참조)

화살표 함수는 짧고 간결한 함수 선언 문법.

this를 선언 위치의 컨텍스트에서 렉시컬 바인딩하며, arguments 객체는 지원하지 않는다.

간단한 콜백 함수, 배열 메서드, 비동기 코드에서 자주 사용된다.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글