08_nodejs와 express

Lukaid·2023년 9월 14일
0

boostcamp

목록 보기
16/20

이번 플젝에서 express를 사용하게 되었다. 요새는 프론트도 SSR을 위해 express를 띄워두고 서비스 한다던데 이번 기회에 잘 알아두자! 미들웨어를 중심으로 공식문서를 보며 정리해보았다.



Node.js란?

Node.js에 대해 깊게 공부하면 한도 끝도 없으니, 간단히만 알아보자.

Node.js는 JavaScript 런타임인데, 그냥 내 로컬 환경에서도 JavaScript 언어를 실행할 수 있게 해주는 녀석이라고 생각하면 되겠다. 원래 JavaScript는 브라우저에서 밖에는 동작하지 않았다.

공식문서에는 Node.js를 Chrome V8 JavaScript 엔진으로 빌드 된 JavaScript 런타임(환경)이라고 설명하고 있다. 쉽게 말해서 Node.js란 JavaScript를 실행할 수 있는 프로그램이라고 볼 수 있다.

Node.js가 만들어지기 전 브라우저만이 JavaScript 실행기였다면, Node.js가 만들어 진 후에는 JavaScript 실행기가 하나 더 만들어졌다고 말할 수 있다.

이 외에도, 논블로킹, 비동기, 이벤트 루프, C++ addon, libuv, 이그니션, 터보팬, jit 컴파일러 등의 키워드가 Node.js와 함께 등장하지만, 여기서 자세히 다루지는 않겠다.






Express

Express는 Node.js 진영에서 가장 많이 사용되는 웹 프레임워크이다. Express의 공식문서에 소개된 문장은 다음과 같다.

Fast, unopinionated, minimalist web framework for Node.js

빠르고 미니멀하다는 것 까지는 알겠는데, unopinionated는 뭘까? Opinionated Framework에는 Django나 Spring boot가 있다. 이런 프레임워크는 개발자가 프레임워크가 제공하는 기능을 사용하기 위해서는 프레임워크가 요구하는 규칙을 따라야 한다. 반대로, unopinionated는 개발자가 프레임워크가 제공하는 기능을 사용할지 말지를 결정할 수 있다는 것이다. 프레임워크의 디자인 철학 중 하나이다.

unopinionated는 다음과 같은 특징을 가진다.

  • 미리 정의된 규칙이나 강제성이 적다: Express는 개발자에게 많은 자유를 제공하며, 애플리케이션의 구조나 패턴에 대해 강력한 제약을 가하지 않는다. 따라서 Express 애플리케이션을 개발할 때, 개발자가 많은 결정을 내릴 수 있다.

  • 최소한의 설정과 제한된 추상화: Express는 가능한 최소한의 설정을 제공하며, 웹 애플리케이션의 구조를 개발자가 직접 설계하고 관리해야 한다. 이로써 개발자가 자신의 프로젝트에 적합한 아키텍처와 패턴을 선택할 수 있다.

  • 미리 정의된 폴더 구조나 파일 구조가 없음: Express는 프로젝트의 폴더 구조나 파일 구조에 대해 엄격한 규칙을 강제하지 않는다. 따라서 개발자가 자신의 프로젝트에 맞게 구조를 설계할 수 있다.

django를 주로 사용했던 나에게는 다소 머리가 아픈 부분이다... 이런 구조 때문에 express는 django나 spring보다 더 유연한 프레임워크로 간주된다. 하지만, 이런 유연성은 개발자가 프로젝트의 구조를 직접 설계해야 하고 더 큰 책임을 부여하는 단점으로 이어지기도 한다.


express는 놀랍도록 쉽고 별게 없다. 다음과 같이 express를 설치하고, 간단한 서버를 만들어보자.

$ npm install express
// app.js
import express from "express";

const app = express();

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

app.listen(3000, () => {
  console.log("백엔드 API 서버가 켜졌어요!!!");
});
$ node app.js

localhost:3000/qqq로 접속하면 Hello World!가 출력된다. 물론 좋은 구조는 아니다.

express-generator

express-generator는 이렇게 자유분방한 express에 구조를 한스푼 추가해주는 녀석이다. express-generator를 설치하고, express 애플리케이션을 생성해보자.

# mac에서 권한요류 발생시 sudo 추가
npm install express-generator -g
# express가 안된다면 npx express-generator 명령으로
express hello-express --view=ejs # ejs는 템플릿 엔진
cd hello-express
npm install
npm start

이렇게 생성된 파일구조는 다음과 같다.

.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.ejs
    ├── index.ejs
    └── layout.ejs

7 directories, 9 files

위에서 생성된 각 파일의 역할은 다음과 같다.

  • app.js: 애플리케이션의 진입점. Express 애플리케이션의 설정 및 미들웨어 설정을 담당하는 파일이다.
  • bin/www: 서버를 시작하는 스크립트 파일. 이 파일을 실행하여 Express 애플리케이션을 서버로 띄울 수 있다.
  • public/: 정적 자원 (images, css, JavaScript 파일 등)을 저장하는 디렉토리. 클라이언트 측에서 요청할 수 있는 정적 파일들이 여기에 저장된다.
  • routes/: 라우팅과 관련된 파일을 저장하는 디렉토리. 주로 웹 애플리케이션의 다양한 경로에 대한 요청을 처리하는 라우팅 코드가 여기에 위치한다.
  • views/: EJS 템플릿 파일을 저장하는 디렉토리. EJS 템플릿을 사용하여 웹 페이지를 렌더링하는 데 사용된다.
  • routes/index.js: 기본 라우팅 파일로, 애플리케이션의 홈페이지와 관련된 경로를 처리한다. 예를 들어, 홈페이지 라우트('/')가 여기에 정의된다.
  • views/index.ejs: 홈페이지에 대한 EJS 템플릿 파일. 이 템플릿 파일은 routes/index.js에서 렌더링되어 클라이언트에게 전달된다.
  • package.json: 프로젝트의 의존성 및 스크립트 명령을 정의하는 파일. npm install 명령을 사용하여 프로젝트의 의존성을 설치하고 npm start 명령을 사용하여 서버를 시작할 수 있다.
  • package-lock.json: 의존성 패키지의 정확한 버전 및 의존성 트리를 기록하는 파일. 일반적으로 직접 편집하지 않으며 npm이 자동으로 관리한다.
  • node_modules/: 프로젝트의 Node.js 모듈 및 패키지들이 설치되는 디렉토리. 질량이 중성자의 그것과 맞먹거나 그 이상이다.





middleware

미들웨어는 또 뭘까? 웹 프레임워크에서 가장 중요한 개념이라고 봐도 무방하겠다. 프레임워크에서의 미들웨어는 웹 애플리케이션 또는 API의 요청(request)과 응답(response) 처리 중간에 위치하는 소프트웨어 구성 요소이다. 미들웨어는 클라이언트로부터의 요청이 서버에 도달하기 전 또는 서버에서 응답이 클라이언트에게 보내지기 전에 요청 또는 응답을 가로채고 수정, 분석 또는 다양한 작업을 수행한다. (뭔가 가로챈다고 하니 어감이 별로지만 꼭 필요한 작업이다.) 미들웨어를 사용하면 웹 애플리케이션의 동작을 확장하고 수정할 수 있으며, 코드를 모듈화하고 재사용성을 높일 수 있다.


express에서의 middleware

express에도 당연히 middleware가 존재한다. 미들웨어의 주요 특징은 다음과 같다.

  • 순서 중요성: 미들웨어는 순서대로 실행된다. 따라서 미들웨어 함수를 추가한 순서에 따라 처리가 이루어지므로, 함수를 추가하는 순서를 잘 생각해야 한다.
  • 요청 처리 및 응답 수정: 요청 객체(req)와 응답 객체(res)를 수정하거나 그 내용을 검사할 수 있다.
  • next 함수 호출: 미들웨어 함수는 next라는 함수를 호출하여 다음 미들웨어나 라우트 핸들러로 제어를 전달한다. 이를 통해 여러 미들웨어를 연결하고 실행 순서를 관리할 수 있다.
  • 라우팅과 미들웨어: Express에서는 경로별로 미들웨어를 추가할 수 있다. 이는 특정 경로로 들어오는 요청에 대해 특정 미들웨어를 실행하도록 할 때 유용하다.
  • 미들웨어 스택: Express 애플리케이션은 미들웨어 스택을 가지고 있으며 요청이 처리되는 동안 이 스택을 따라 미들웨어 함수가 실행된다.

image




미들웨어의 종류

미들웨어에는 다음과 같은 다섯가지 종류가 있다.

  • Application-level middleware
  • Router-level middleware
  • Error-handling middleware
  • Built-in middleware
  • Third-party middleware

애플리케이션 레벨 미들웨어 (Application-level middleware)

가장 많이 본 미들웨어이다. app.use()app.METHOD() (GET, PUT 또는 POST 등)이 그것이다. app instance에 바인드 하여 앱 전체에 적용되는 미들웨어이다.

var app = express();

app.use("/user/:id", function (req, res, next) {
  console.log("Request Type:", req.method);
  next();
});

라우터 레벨 미들웨어 (Router-level middleware)

라우터 레벨 미들웨어는 express.Router() 인스턴스에 바인드된다는 점을 제외하면 애플리케이션 레벨 미들웨어와 동일한 방식으로 작동한다.

var app = express();
var router = express.Router();

router.use("/user/:id", function (req, res, next) {
  console.log("Request Type:", req.method);
  next();
});

// mount the router on the app
app.use("/", router);

이렇게만 보면 어플리케이션 레벨과 라우터 레벨의 미들웨어가 어떤 차이가 있는지 잘 모르겠다. 이에 대해 더 자세히 공부해보자.


Application-level middleware Vs Router-level middleware

  1. 적용 범위:

    • Application-level middleware:

      • 애플리케이션 레벨의 미들웨어는 애플리케이션 전체에 적용된다. 즉, 모든 경로와 라우트에 대한 요청에 대해 실행된다.
      • 예를 들어, express.json() 미들웨어는 HTTP 요청의 본문 데이터(JSON 형식)를 파싱하여 req.body 객체에 저장하는 데 사용된다. 이 미들웨어는 애플리케이션 어디에서든 사용 가능하다.
    • Router-level middleware:

      • 라우터 레벨의 미들웨어는 특정한 경로나 라우터에만 적용된다. 즉, 특정 경로에 대한 요청에만 이 미들웨어가 실행된다.
      • express.Router()를 사용하여 생성한 라우터 객체에 미들웨어를 추가할 때 이 미들웨어는 해당 라우터와 그 하위 경로에서만 실행된다.
  2. 적용 방법:

    • Application-level middleware:

      • Application-level 미들웨어는 app.use() 또는 app.<HTTP_METHOD>()를 통해 Express 애플리케이션에 추가된다.
      • 예를 들어, 모든 요청에 대한 로깅을 하려면 app.use()를 사용하여 애플리케이션 전체에 로깅 미들웨어를 추가할 수 있다.
    • Router-level middleware:

      • Router-level 미들웨어는 router.use() 또는 router.<HTTP_METHOD>()를 통해 특정 라우터에 추가되다.
      • 예를 들어, /api 경로에 대한 라우터에만 인증 미들웨어를 추가하려면 해당 라우터에서 router.use()를 사용하여 미들웨어를 추가한다.
  3. 적용 순서:

    • Application-level middleware:

      • Application-level 미들웨어는 애플리케이션의 모든 요청에 대해 먼저 실행된다. 따라서 이 미들웨어는 요청 처리에 가장 먼저 영향을 미친다.
      • 예를 들어, 로깅 미들웨어를 Application-level로 추가하면 모든 요청에 대한 로그가 먼저 기록된다.
    • Router-level middleware:

      • Router-level 미들웨어는 특정 라우터 또는 경로에 대한 요청 처리에서만 실행된다. 따라서 해당 라우터로 들어오는 요청에만 영향을 미친다.
      • 예를 들어, /api 경로에 대한 라우터에만 인증 미들웨어를 추가하면 /api로 들어오는 요청에만 인증이 적용된다.

Application-level Middleware 예시:

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

// Application-level 미들웨어: 모든 요청에 대해 실행된다.
app.use((req, res, next) => {
  console.log("Application-level Middleware");
  next(); // 다음 미들웨어 또는 라우트 핸들러로 제어를 전달한다.
});

// 라우트 핸들러: Application-level 미들웨어 다음에 실행된다.
app.get("/", (req, res) => {
  res.send("홈 페이지");
});

app.listen(3000, () => {
  console.log("서버가 3000 포트에서 실행 중입니다.");
});

위 코드는 app.use()를 사용하여 Application-level 미들웨어를 추가하고, 모든 요청에 대해 실행되는 예시이다.


Router-level Middleware 예시:

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

// Router-level 미들웨어: 특정 라우터에만 적용된다.
const router = express.Router();

router.use((req, res, next) => {
  console.log("Router-level Middleware");
  next(); // 다음 미들웨어 또는 라우트 핸들러로 제어를 전달한다.
});

// 라우트 핸들러: Router-level 미들웨어 다음에 실행된다.
router.get("/api", (req, res) => {
  res.send("API 라우트");
});

app.use("/myapp", router); // 라우터를 /myapp 경로에 마운트한다.

app.listen(3000, () => {
  console.log("서버가 3000 포트에서 실행 중입니다.");
});

위 코드는 router.use()를 사용하여 Router-level 미들웨어를 추가하고, 특정 라우터(/myapp 경로)에만 적용되는 예시이다. Router-level 미들웨어는 /myapp/api 경로에 대한 요청에만 실행된다.


에러 핸들링 미들웨어 (Error-handling middleware)

에러 핸들링 미들웨어는 말 그대로 에러를 처리하고 응답을 생성하는 데 사용된다. 이 미들웨어는 항상 4개의 인수가 필요하다. (err, req, res, next) 에러가 발생하면 이 미들웨어가 호출되어 에러를 처리하고 클라이언트에게 에러 응답을 보낸다.

app.use(function (err, req, res, next) {
  console.error(err.stack); // 에러가 발생하면 콘솔에 에러 스택을 출력 후
  res.status(500).send("Something broke!"); // 클라이언트로 500코드와 함께 응답 메세지를 보낸다.
});

기본 제공 미들웨어 (Built-in middleware)

Express에서 기본적으로 제공해주는 미들웨어이다. express.static (정적 파일 제공), express.json (JSON 파싱), express.urlencoded (URL-encoded 데이터 파싱) 등이 있다.


서드파티 미들웨어 (Third-party middleware)

기본 제공 미들웨어와 반대로, Express에서 제공하지 않는 미들웨어이다. 대표적으로 body-parser, cookie-parser, multer 등이 있다. 이들을 사용하려면 별도로 설치해야한다.

npm install cookie-parser
var express = require("express");
var app = express();
var cookieParser = require("cookie-parser");

// load the cookie-parsing middleware
app.use(cookieParser());





App apis

express() 호출을 통해 생성되는 app 객체에서 제공해 주는 메소드들이다.

app.use()

미들웨어를 추가하는 메소드이다. 미들웨어는 요청과 응답 사이에서 실행되는 함수이다. 미들웨어 함수는 다음과 같은 세 개의 인수를 가진다.

  • req: 요청 객체
  • res: 응답 객체
  • next: 다음 미들웨어 함수를 호출하는 함수

app.<HTTP_METHOD>()

HTTP 메소드에 따라 라우트를 추가하는 메소드이다.

app.listen()

서버를 시작하는 메소드이다. app.listen() 메소드는 다음과 같은 세 개의 인수를 가진다.

  • port: 서버가 바인딩할 포트 번호
  • host: 서버가 바인딩할 호스트 주소
  • backlog: 연결 대기열의 최대 길이

app.all()

모든 HTTP 메소드에 대해 라우트를 추가하는 메소드이다. app.all() 메소드는 다음과 같은 두 개의 인수를 가진다.

  • path: 라우트 경로
  • callback: 라우트 핸들러

app.param()

라우트 매개변수를 설정하는 메소드이다. app.param() 메소드는 다음과 같은 두 개의 인수를 가진다.

  • name: 매개변수 이름
  • callback: 콜백 함수

app.path()

애플리케이션의 경로를 반환하는 메소드이다. app.path() 메소드는 다음과 같은 하나의 인수를 가진다.

  • path: 애플리케이션의 경로
profile
풀스택 지향 웹개발자 이성우입니다.

0개의 댓글