express 튜토리얼

sham·2025년 1월 15일

개요

Node.js를 위한 express는 웹 프레임워크이다. 쉽고 빠르게 웹 서버를 만들 수 있다는 특징이 있다.

express-generator

express를 활용해 웹 서버의 기초가 되는 코드를 미리 작성해둔 뼈대를 express-generator를 통해 생성할 수 있다.

CRA와 유사한 역할을 한다고 생각하면 편리하다.

본문

동작 방식

단순한 express로 동작하는 서버이다. 한 줄씩 읽어가며 어떻게 동작하는지 확인해보자.

import express from "express";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();

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

app.use((req, res, next) => {
  console.log("모든 미들웨어");
  next();
});

app.use("/test", (req, res, next) => {
  console.log("test 미들웨어");
  next();
});

app.use("/hello", (req, res, next) => {
  console.log("hello 미들웨어");
  next();
});

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

app.get("/test", (req, res) => {
  // es6에서는 __dirname이 없음
  res.sendFile(__dirname + "/test.html");
});

const app = express()

  • Express는 Node.js에서 사용되는 웹 애플리케이션 프레임워크.
    • 서버를 더 쉽게 만들 수 있도록 도와주는 도구
  • express()로 Express 애플리케이션을 생성할 수 있다. 서버의 역할을 하며, 요청을 처리하고 응답을 반환하는 객체를 생성할 수 있다.
    • app 객체는 Express 서버 전체를 나타내며, 라우팅, 미들웨어 등 모든 설정을 이 객체에서 처리할 수 있다.

app.listen()

  • 서버를 특정 포트에서 실행한다.
  • 인자로 포트번호와 콜백함수를 받는다. 서버가 실행되면 콜백함수가 호출된다.

app.(get | post | put | delete)()

  • HTTP의 각 메서드에 대응하는 함수이다.
    • 이벤트를 등록한다고 생각해도 좋다.
  • 인자로 url과 콜백함수를 받는다. url와 메서드가 대응되는 요청이 들어오면 콜백함수가 호출된다.
    • get의 경우 해당 포트의 url에 접속하는 것만으로도 호출되게 된다.
    • res.send: 클라이언트에 응답을 보낼 수 있다.

app.use((req, res, next) => {}

  • 요청과 응답 사이, 미들웨어 처리를 가능케 하는 함수이다.
    • 클라이언트에서 요청 시 먼저 호출 되고, next()를 사용해서 다음 미들웨어나 응답 함수로 넘겨주어야 한다.
  • 첫번째 인자에 url을 넣으면 특정 엔드포인트에 미들웨어를 적용시킬 수 있다.
    • 아무런 인자도 넣지 않으면 모든 요청에 해당하는 미들웨어가 된다.

Routing

라우팅은 각 URI(엔드 포인트)와 HTTP Method로 이루어진 요청에 대응하는 응답을 처리하는 것이다.

위처럼 express()로 생성한 객체에 하나하나 달아줄 수도 있겠지만, 대응해야 할 요청이 많아지면 하나의 파일에서 전부 관리하기란 불가능에 가깝다.

그렇기에 express에서 Router()로 객체를 만들고 하나의 url 당 하나의 라우터로 CRUD를 관리해주는 것이다.

파일 이름 없이 달랑 index가 적힌 것은 express-generator 설정 때문. view engine이 jade로 되어있고 views의 jade 확장자 파일을 렌더링하는 것을 확인할 수 있다.

// routes/index.js

var express = require("express");
var router = express.Router();

/* GET home page. */
router.get("/", function (req, res) {
  res.render("index", { title: "Express" });
});

router.post("/", function (req, res) {
  res.send("Got a POST request");
});

module.exports = router;

만들어준 router 들은 app.js 파일에서 미들웨어로 하나씩 붙여주도록 한다.

// app.js

var express = require('express');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);

Route paths

라우팅 시, string으로 라우팅하는 방식 말고도 string 패턴, 정규 표현식을 활용할 수도 있다.

  • ?(optional)를 활용해서 acd, abcd를 포함하도록 한다.
  • +를 활용해서 abcd, abbcd, abbbbcd를 포함하도록 한다.
router.get("/ab?cd", function (req, res) {
  res.send("ab?cd");
});
router.get("/ab+cd", function (req, res) {
  res.send("ab+cd");
});

Route parameters

url 안에 변수가 담겨 오는 URL 세그먼트를 처리할 수 있다.

app.get('/users/:userId/books/:bookId',function (req, res) {
  res.send(req.params)//  { "userId": "34", "bookId": "8989" }
})

정적 파일 제공

express.static(root, [options])

express.static 미들웨어를 활용해서 정적 파일을 제공해줄 수 있다.

  • root : string, 정적 파일이 위치한 경로가 들어간다.
  • options : ****속성을 키값으로 하는 객체가 들어간다.

app.use(express.static(path.join(__dirname, "public")));

  • express.static로 생성한 미들웨어에 현재 [디렉토리 경로]/public 에 해당하는 폴더를 정적으로 제공한다고 설정해주고, 이를 app에서 미들웨어로 사용하는 모습이다.
  • 옵션을 비워두면 default 값을 사용한다.
// app.js 

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();

app.use(express.static(path.join(__dirname, "public")));

Path 모듈?

Node.js가 기본 제공하는 모듈이다. 이름처럼 경로에 관련된 기능을 제공한다.

__dirname 역시 Node.js에서 기본 제공하는 변수로, node 파일의 경로를 제공한다. (ES Modules에서는 존재하지 않는다!)

전역 변수이므로 어디서든 접근 가능하고, app.js에서 path.join에 의해 현재 경로에 "public"이라는 경로와 더해져서 우리가 원했던 public의 경로를 얻을 수 있게 되었다.

nginx 같은 프록시 서버가 달린 실제 서비스에서는 프록시 서버 단에서 메모리 캐시를 이용해 정적 파일들을 제공하는 편이라고 한다. 프록시 서버가 달려 있다면 굳이 Express까지 거쳐서 제공되야 할 필요가 없기에…

미들웨어

미들웨어 함수는 req(요청) 객체, res(응답) 객체, 그리고 어플리케이션 요청 응답 사이클 도중 그 다음의 미들웨어 함수에 제어 할 수 있는 함수이다.

클라이언트에게 요청이 오고 그 요청을 보내기 위해 응답하려는 중간(미들)에 모종의 작업을 하는, 거쳐가는 함수라고 할 수 있겠다.

순서적으로 처리되며, next(), next('route')를 통해 흐름을 제어할 수 있다.

기본적으로 모든 요청에 대해 실행되며, 특정 경로에만 반응하는 라우팅 미들웨어도 있다.

app.use((req, res, next) => {}

사용자 정의 미들웨어를 생성할 때 위와 같은 양식을 띈다.

req, res의 요청 응답 객체를 조작할 수 있으며, next()를 호출해서 다음 미들웨어를 호출해야만 한다. (안 그러면 넘어가지를 못한다.)

첫번째 인자에 url을 추가하면 해당 경로에만 반응하는 라우팅 미들웨어가 된다.

// 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");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/", indexRouter);
app.use("/users", usersRouter);

// catch 404 and forward to error handler
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;

애플리케이션 레벨 미들웨어

app.use()app.METHOD() 함수를 사용하여 app 객체에 바인딩되는 미들웨어를 의미한다.

경로가 없는 미들웨어, 특정 경로에 대응하는 미들웨어가 해당된다.

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

app.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})

app.get('/user/:id', (req, res, next) => {
  res.send('USER')
})

라우터 레벨 미들웨어

express.Router()를 통해 생성된 객체를 통해 router.use() 또는 router.METHOD() (METHODget, post, put 등 HTTP 메서드) 함수를 사용해 설정한다.

주로 특정 root url을 기점으로 기능이나 로직별로 라우팅을 나눠서 관리하기 위해 사용된다.

onst express = require('express')
const app = express()
const router = express.Router()

router.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})

router.use('/user/:id', (req, res, next) => {
  console.log('Request URL:', req.originalUrl)
  next()
}, (req, res, next) => {
  console.log('Request Type:', req.method)
  next()
})

router.get('/user/:id', (req, res, next) => {
  console.log(req.params.id)
  res.render('special')
})

app.use('/', router)

에러 핸들링 미들웨어

기본 미들웨어의 콜백 함수 인자에 err가 붙은 미들웨어로, 인자 개수에 의해 정의된다.

에러를 처리하기 위한 미들웨어이며, 에러 발생 시 자동으로 라우팅되게 된다.

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

서드파티 미들웨어

Express 외부에서 제공되는 미들웨어로, 특정 기능을 쉽게 추가하기 위해 사용된다.

NPM으로 설치하는 게 일반적.

const express = require('express')
const app = express()
const cookieParser = require('cookie-parser') // 대표적인 서드파티 미들웨

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

에러 핸들링

앞서 에러 핸들링 미들웨어로 에러를 처리한다는 것을 알았다.

에러가 throw 된다면, 다른 미들웨어는 전부 무시하고 에러 핸들링 미들웨어로 바로 라우팅이 된다.

에러 객체를 받을 수 있으며, 사용자 정의 라우터처럼 특정 url에서 발생하는 에러에 대해 처리하는 것도 가능하다.


router.use("/page", (err, req, res, next) => {
  console.error(err.stack);
  console.log("page error");
  res.status(500).send("Something broke!");
});

router.use((err, req, res, next) => {
  console.error(err.stack);
  console.log("root error");
  res.status(500).send("Something broke!");
});

레퍼런스

node.js express 1. express란??, express generator, directory 구조

[EXPRESS] 📚 미들웨어 이론 & 실용 💯 정리

Express routing

profile
씨앗 개발자

0개의 댓글