Express에 대해 알아보자

Daehyeon Yun·2024년 8월 7일
post-thumbnail

📖 Reference
📎 https://m.blog.naver.com/hj_kim97/222913693753


💡 Express란 무엇일까?

Express 는 현재 가장 인기있는 Node.js 프레임워크로, 웹 및 모바일 애플리케이션을 위한 강력한 기능을 제공하는 간결하고 유연한 프레임워크입니다. 이를 통해 가볍고 빠르게 서버를 구축할 수 있습니다.

Express는 최소한의 기능만들 탑재하였지만, 개발자들이 거의 모든 웹 개발의 문제를 다루는 호환성 있는 미들웨어 패키지로 쿠키, 세션, 로그인, URL 파라미터, GET/POST 데이터, 보안 헤더와 그 외 많은 라이브러리로 유연한 개발이 가능하다.

Node.jsHTTP 내장 모듈을 사용하여 웹 서버를 띄우는 경우와 Express 를 사용하여 웹 서버를 띄우는 경우를 아래에 정리하였다.

// Node.js에서 HTTP 내장 모듈을 사용하여 웹 서버 구축
const http = require('http');

http.createServer((request, response) => {
	response.writeHead(200, {'Content-Type':'text/html'});
	response.write("Hello HTTP WebServer!");
	response.end();
}).listen(8000, () => { console.log("Server is Running!"); });

// Express를 이용한 서버 구축
const express = require('express');
const app = express;
const port = 3000;

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

app.listen(port, () => {
	console.log(`Express is Listening on port ${port}`)
});

🔥 Express의 특징

  • 가볍고 유연한 구조 : Express는 최소한의 기능만 탑재되어 있으며, 개발자가 필요한 미들웨어를 선택하여 사용할 수 있다.

  • 미들웨어 지원 : 미들웨어를 활용하여 요청과 응답 과정 중간에 특정한 동작을 수행할 수 있다.

  • 직관적인 라우팅 : URIHTTP 요청 메서드에 따라 클라이언트 요청에 대한 응답을 결정하는 라우팅이 편리하게 구현된다.


👌Express의 장단점

  • 장점
    • 가볍게 웹 서버를 구현할 수 있습니다.
    • 미들웨어를 통한 확장성이 뛰어납니다.
    • 직관적으로 라우팅 처리를 할 수 있습니다.
  • 단점
    • 최소한의 기능만 제공하므로, 특정한 기능을 추가하기 위해서는 별도의 미들웨어 혹은 패키지를 사용해야 합니다.

🤔 어떤 경우에 Express를 사용하나?

Express 는 주로 아래와 같은 경우에 많이 사용합니다.

  • 웹 애플리케이션 또는 API 서버를 개발할 떄 주로 사용합니다.
  • 가볍고 빠른 서버를 구축하고자 할 때 Express 를 사용하여 구축합니다.

❓ 미들웨어(Middleware)란 무엇인가?

미들웨어(Middleware) 는 운영 체제와 해당 운영 체제에서 실행되는 애플리케이션 사이에 존재하는 소프트웨어를 말합니다. 기본적으로 숨겨진 변환 계층 으로 동작하며, 분산 애플리케이션의 통신 및 데이터 관리를 가능하게 합니다.

미들웨어(Middleware) 의 몇 가지 예시는 아래와 같다.

  1. 웹 서버 미들웨어 : 웹 서버와 클라이언트 간의 요청과 응답을 처리하는 미들웨어. 로깅, 보안, 세션 관리 등의 작업을 수행한다.
  2. 데이터베이스 미들웨어 : 데이터베이스와 애플리케이션 간의 상호 작용을 관리하는 미들웨어. 데이터베이스 연결, 쿼리 실행, 트랜잭션 관리 등을 담당한다.
  3. 메시징 미들웨어 : 메시지 큐를 통해 애플리케이션 간 메시지를 전달하고 처리하는 역할을 수행한다.

💡 Express의 라우팅처리

라우팅(Routing) 이란?
URI(또는 경로) 및 특정한 HTTP 요청 메소드(GET, POST 등)인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말한다.

이미지 출처 : https://expressjs.com/ko/quide/writing-middleware.html

app._method(_path, _handler)
  • app : express의 인스턴스
  • _method : HTTP 요청 메소드(GET, POST 등)
  • _path : 서버에서의 경로
  • _handler : 라우트(Route) 가 일치할 때 실행되는 함수

Express 는 아래와 같은 HTTP 메소드 에 해당하는 라우팅 메소드를 지원한다.

get, post, put, head, delete, options, trace, copy, 
lock, mkcol, move, purge, propfind, proppatch, unlock,
report, mkactivity, checkout, merge, m-search, notify, 
subscribe, unsubscribe, patch, search, connect
// HTTP GET Request
app.get('/', function (req, res) {
  res.send('Hello World!');
});

// HTTP POST Request
app.post('/', function (req, res) {
  res.send('Got a POST request');
});

// HTTP PUT Request
app.put('/user', function (req, res) {
  res.send('Got a PUT request at /user');
});

// HTTP DELETE Request
app.delete('/user', function (req, res) {
  res.send('Got a DELETE request at /user');
});

// 모든 HTTP Request
app.all('/customer', function (req, res) {
  res.send('Got a ALL request at /customer');
});

💡라우트(Route) 경로란?

라우트(Route) 경로는 요청 메소드와의 조합을 통해 요청이 이루어질 수 있는 엔드포인트를 정의한다. 문자열, 문자열 패턴, 정규식 등을 이용하여 만들 수 있다.

  • 문자열 예시
    • '/', '/about', '/about/customer'
  • 문자열 패턴 예시
    • '/ab?cd': 'b?'는 문자 'b'가 0개 혹은 1개 있다는 것을 의미('/acd', '/abcd')
    • '/ab+cd': 'b+'는 문자 'b'가 1개 이상 있다는 것을 의미('/abcd', '/abbcd', '/abbbcd', ..)
    • '/ab*cd': 문자 'ab'와 문자 'cd' 사이에 문자가 없거나 혹은 어떤 문자도 올 수 있다는 것을 의미('/abcd', 'abXcd', '/abXYZcd', ..)
    • '/ab(cd)?e': '(cd)?'는 문자 'cd'가 0번 혹은 1번 있을 수 있다는 것을 의미('/abe', '/abcde')
  • 정규식 예시
    • /a/: 클라이언트에서 요청한 라우트 경로에 'a'가 포함되어 있는 경우-
    • /^insert/: 클라이언트에서 요청한 라우트 경로가 문자 'insert'로 시작하는 경우-
    • /^\/[0-9]+$/: 클라이언트에서 요청한 라우트 경로가 숫자인 경우
// 문자열
app.get('/about', function (req, res) {
  res.send('about');
});

// 문자열 패턴
app.get('/ab?cd', function(req, res) {
  res.send('ab?cd');
});

// 정규식
app.get(/a/, function(req, res) {
  res.send('/a/');
});

// Path Variable
app.get('/user/:userId/item/:itemId', (req, res) => {
  const { userId, itemId } = req.params;
  res.send(`userId: ${userId}, itemId: ${itemId}`);
});

💡라우트(Route) 핸들러

Route Handler는 클라이언트 요청에 따라 라우트가 일치할 때 실행되는 콜백 함수를 말한다.

  • req: Request로 클라이언트의 요청 정보를 담고 있다.
  • res : Response로 클라이언트에게 응답하기 위한 정보를 담고 있다.
  • next : 다음으로 실행 될 미들웨어 함수를 가리키는 오브젝트이다.
// 콜백 함수만 있는 경우
app.get('/home/a', (req, res) => {
	res.send("Hello World!");
});

// 콜백 함수에 next를 적용하여 2개 이상의 콜백 함수 실행
app.get('/home/b', (req, res, next) => {
	console.log("첫 번째 cb");
	next();
}, (req, res) => {
	res.send("두 번째 cb");
});

// 콜백 함수를 배열로 처리하여 라우트 처리 예시
var firstCallback = (req, res, next) => {
	console.log("First Callback");
	next();
}

var secondCallback = (req, res, next) => {
	console.log("Second Callback");
	next();
}

var thridCallback = (req, res) => {
	console.log("Thrid Callback");
}

app.get('/home/c', [firstCallback, secondCallback, thridCallback]);

💡 Express의 응답 메소드 정리

Express는 클라이언트로부터 요청이 오면 라우트에서는 다음 메소드 중 한 가지 방법으로 응답을 전송하고 요청(Reqeust) <-> 응답(Response) 주기를 종료할 수 있다.

💀 만약, 라우트 핸들러 함수로부터 아래의 메소드 중 어느 하나라도 호출되지 않는 경우, 클라이언트 요청은 정지된 채로 방치된다.

  • res.download() : 파일이 다운로드 되도록 프롬프트
  • res.end() : 응답 프로세스 종료
  • res.json() : JSON 응답을 전송
  • res.jsonp() : JSONP 지원을 통해 JSON 응답 전송
  • res.redirect() : 요청의 경로 재지정
  • res.render() : 뷰(View) 템플릿 렌더링
  • res.send() : 다양한 유형의 응답을 전송 (가장 많이 사용)
  • res.sendFile() : 파일을 옥텟(octet, 8비트로 된 데이터) 스트림 형태로 전송
  • res.sendStatus() : 응답 상태 코드를 설정한 후 해당 코드를 문자열로 표현한 내용을 응답 Body에 담아 전송

💡Express의 라우트(Route) 체인

Express에서 app.route() 를 이용하면 라우트 경로에 대하여 체인(Chain, 연결) 가능한 라우트 핸들러를 작성할 수 있다.

❕경로는 한 곳에 지정되어 있기에 모듈식 라우트를 작성한다면 중복성이 감소하여 코드를 효율적으로 관리할 수 있다.

// Router Chain : 하나의 라우트로 각 라우트의 메소드 처리
app.route('/customer')
	.get((req, res) => { // GET Request 처리
		res.send("고객 정보 조회");
	})
	.post((req, res) => {  // POST Request 처리
		res.send("신규 고객 추가");
	})
	.put((req, res) => { // PUT Request 처리
		res.send("고객 정보 수정");
	})
	.delete((req, res) => { // DELETE Request 처리
		res.send("고객 정보 삭제");
});

💡 Express에서의 app.use()

app.use() 함수는 Express 애플리케이션에서 항상 실행하는 미들웨어의 역할을 수행한다. app.get(), app.post() 등과 달리 요청 URL을 지정하지 않아도 app.use() 를 사용할 수 있으며, 해당 경우 URL에 상관 없이 애플리케이션이 요청을 수신 할 때마다 매번 실행된다.

  • app.use() 는 지정된 경로에 미들웨어 함수를 마운트 하는데 사용한다.
  • 모든(또는 특정) 요청에 대한 공통 로직을 처리하기 위해 사용한다.
  • 주로 애플리케이션에 대한 미들웨어를 설정하는데 사용된다. (모듈식 라우터, 에러 핸들링, 공통 미들웨어 등)
// app.use() 기본 문법
app.use(_path, _callback)
// _path : 미들웨어 함수가 호출되는 경로. (경로, 경로패턴 또는 경로와 일치하는 정규식 패턴을 나타냄)
// _callback : 미들웨어 함수 또는 미들웨어 함수의 시리즈/배열

// 모든 요청에 대한 핸들링 (경로, path를 지정하지 않은 경우 모든 요청에 대한 공통 핸들링 지정 가능
app.use((req, res, next) => {
	console.log("현재시간 : ", Date.now());
	next();
});

// 특정 요청에 대한 핸등링 (URL 파라미터)
app.use('/page/:id', (req, res, next) => {
	... // 해당 요청에 대한 처리 로직
	next();
});

💡 Express의 모듈식 라우터

Express 에 존재하는 Router() 클래스를 사용하면 라우트 처리를 하나의 파일에서 하는 것이 아니라 여러 개의 파일로 분리하여 마치 컴포넌트처럼 각각의 용도에 맞게 구현하여 사용할 수 있다.

😲 분리한 요청 라우터 모듈은 웹 서버를 구동하는 app.js 파일에 app.use() 함수를 이용하여 연결할 수 있다.

app.use(_path, _라우터 객체)

// customer.js
const express = require('express');
const router = express.Router();

router
	.get((req, res) => {
		res.send("고객 정보 조회");
	})
	.post((req, res) => {
		res.send("신규 고객 추가");
	})
	.put((req, res) => {
		res.send("고객 정보 수정");
	})
	.delete((req, res) => {
		res.send("고객 정보 삭제");
});

module.exports = router;
// product.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
	res.send("상품 정보 조회");
});

router.post('/insert', (req, res) => {
	res.send("신규 상품 추가");
});

router.put('/update', (req, res) => {
	res.send("상품 정보 수정");
});

router.get('/delete', (req, res) => {
	res.send("상품 정보 삭제");
});

module.exports = router;
// app.js
const express = require('express');
const customerRouter = require('../customer');
const productRouter = require('../product');
const app = express();
const port = 3000;

// 클라이언트 요청 body를 json으로 파싱
app.use(express.json({
	limit: '50mb', // 최대 용량
}));

app.listen(port, () => {
	console.log(`서버가 ${port}에서 실행됩니다.`);
});

// 라우터 지정 후 기본 경로 지정
app.use('/customer', customerRouter);
app.use('/product', productRouter);

🚧 Express에서 에러 처리하기(Exception Handling)

Express에는 애플리케이션에서 발생할 수 있는 모든 에러를 처리하는 에러 핸들러가 내장되어 있다. 이를 사용하면 애플리케이션에서 에러가 발생했을 때 특정 위치에서 에러를 처리할 수 있다.

→ Express에서 일반적인 미들웨어 함수를 정의하는 것처럼 에러 처리를 위한 미들웨어 함수를 정의하면 된다.

→ 에러 처리 미들웨어는 err, req, res, next 총 4개의 매개변수를 사용한다.

⚡ 에러 처리 코드는 라우트 정의 마지막에 진행한다. 또한, err, req, res, next 라는 인자가 있는 함수를 Express에서 에러를 처리하기 위한 함수로 약속되어 있다.

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

app.get('/error/a', (req, res, next) => {
	// 라우터에서 에러가 발생하면 Express가 알아서 이를 캐치한다.
	throw new Error("에러 발생");
});

app.get('/error/b', (req, res, next) => {
	// next를 이용하여 에러 전달
	next(new Error("에러 발생"));
});

// 에러 처리 미들웨어
// 아무 경로도 지정되지 않으면 지정된 URL 이외에 모든 에러를 처리한다.
app.use((err, req, res, next) => {
	// State code 500, 에러 코드 전달
	res.status(500).json({
		statusCode: res.statusCode, 
		errMessage: err.message
	});
});
// 에러 처리 예시
const express = require('exress');
const app = express();

app.get("*", (req, res, next) => {
	const error = new Error("에러 발생");
	error.status = 500;
	next(error);
});

// 에러 발생시 logHandler 실행 이후 errorHandler 실행
app.use(logHandler);
app.use(errorHandler);

// Logger Middleware
const logHandler = (err, req, res, next) => {
	console.error('[' + new Date() + ']\n' + err.stack);
	next(err);
}

// Error Handler
const errorHandler = (err, req, res, next) => {
	res.status(err.status || 500);
	res.send(err.message || "Error");
}

app.listen(3000, () => {
	console.log("Express Server listening on port " + 3000);
});

profile
열심히 살아야지

0개의 댓글