프로그래밍/Node.js

Node.js 웹 개발의 핵심, Express.js 완벽 가이드: 특징, 라우팅, 미들웨어, 템플릿 엔진까지

shimdh 2025. 2. 19. 00:06
728x90

1. Express.js 소개

1.1. Express.js란 무엇인가?

Express.js는 Node.js 환경에서 웹 애플리케이션과 API를 구축하기 위한 사실상의 표준 프레임워크입니다. 간결하고 유연한 구조를 제공하여 개발자들이 서버 측 애플리케이션을 쉽고 빠르게 개발할 수 있도록 돕습니다.

1.2. Express.js를 사용해야 하는 이유

  • 경량성: 핵심 기능만 제공하며, 필요한 모듈과 미들웨어를 추가하여 사용 가능합니다.
  • 유연성: 다양한 HTTP 메서드(GET, POST, PUT, DELETE 등)와 URL 경로에 대한 라우팅을 쉽게 구현할 수 있습니다.
  • 미들웨어 지원: 요청 처리 과정에서 다양한 미들웨어 함수를 사용하여 코드를 구조화하고 재사용성을 높일 수 있습니다.
  • RESTful API 개발 용이: REST 아키텍처 스타일에 따라 리소스를 정의하고 조작하는 데 적합합니다.
  • 활발한 커뮤니티 및 생태계: 많은 개발자들이 사용하고 있어 다양한 자료, 라이브러리, 모듈을 쉽게 찾을 수 있습니다.

1.3. 설치 및 설정

Express.js를 사용하려면 먼저 Node.js가 설치되어 있어야 합니다. Node.js 설치 후 npm(Node Package Manager)을 사용하여 Express를 설치합니다.

npm install express --save

위 명령어는 현재 프로젝트 디렉토리에 express 패키지를 설치하고, package.json 파일에 종속성으로 추가합니다.

1.4. 추가 예제: 간단한 JSON 응답 서버

Express.js를 사용하여 JSON 형식의 데이터를 응답으로 보내는 간단한 서버를 만들어 보겠습니다.

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

// /api/greet 경로에 대한 GET 요청 처리
app.get('/api/greet', (req, res) => {
  res.json({ message: 'Hello, there!', version: '1.0' });
});

// /api/user/:id 경로에 대한 GET 요청 처리 (동적 라우팅)
app.get('/api/user/:id', (req, res) => {
    const userId = req.params.id;
    res.json({ id: userId, name: `User ${userId}`, status: 'active' });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

이 예제는 /api/greet 경로로 요청이 들어오면 JSON 객체를 응답하고, /api/user/:id 경로로 요청이 들어오면 동적으로 생성된 사용자 정보를 JSON 객체로 응답합니다.

2. Express.js 기본 사용법

2.1. Hello World 서버 만들기

Express.js를 사용하여 간단한 웹 서버를 만들어 보겠습니다. 아래 코드는 루트 경로(/)에 GET 요청이 들어오면 "Hello World!" 메시지를 응답으로 보내는 서버입니다.

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

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

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

위 코드를 실행하고 웹 브라우저에서 http://localhost:3000에 접속하면 "Hello World!" 메시지를 확인할 수 있습니다.

2.2. 추가 예제: 다양한 응답 형식

다음은 다양한 응답 형식을 보여주는 예제입니다.

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

// 기본 텍스트 응답
app.get('/text', (req, res) => {
  res.send('This is a plain text response.');
});

// HTML 응답
app.get('/html', (req, res) => {
  res.send('<h1>This is an HTML response</h1>');
});

// JSON 응답
app.get('/json', (req, res) => {
  res.json({ message: 'This is a JSON response', status: 'ok' });
});

// 상태 코드와 함께 응답
app.get('/status', (req, res) => {
  res.status(201).send('Created!');
});

// 파일 응답
app.get('/file', (req, res) => {
    res.sendFile(__dirname + '/public/sample.txt');
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

이 예제에서는 res.send(), res.json(), res.status(), res.sendFile()을 사용하여 다양한 형태의 응답을 제공하는 방법을 보여줍니다. res.sendFile()를 사용하려면 public/sample.txt 파일이 필요합니다.

3. 라우팅 (Routing)

3.1. 라우팅의 개념

라우팅은 클라이언트의 요청 URL과 HTTP 메서드에 따라 적절한 핸들러 함수를 연결하여 응답을 생성하는 과정입니다. Express.js에서는 app.get(), app.post(), app.put(), app.delete() 등의 메서드를 사용하여 라우팅을 설정합니다.

3.2. 기본적인 라우팅

다음은 사용자 목록과 특정 사용자 정보를 조회하는 예제입니다.

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

// 사용자 목록을 가져오는 GET 요청 처리
app.get('/users', (req, res) => {
    const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
    res.json(users);
});

// 특정 사용자 정보를 가져오는 GET 요청 처리
// :id는 동적 라우팅을 나타내며, 요청 URL에 포함된 ID 값이 req.params.id에 저장됩니다.
app.get('/users/:id', (req, res) => {
    const userId = req.params.id;
    res.json({ id: userId, name: 'User ' + userId });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

/users 경로로 GET 요청을 보내면 사용자 목록이 JSON 형식으로 응답되고, /users/1과 같이 특정 ID를 포함한 GET 요청을 보내면 해당 사용자의 정보가 JSON 형식으로 응답됩니다.

3.3. 다양한 HTTP 메서드 처리

HTTP 메서드에 따라 요청을 처리하는 방법은 다음과 같습니다.

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const PORT = 3000;

app.use(bodyParser.json());

// POST 요청을 처리하여 새 사용자 추가
app.post('/users', (req, res) => {
    const newUser = req.body;
    console.log('새로운 사용자 추가:', newUser);
    res.status(201).send('User created successfully.');
});

// PUT 요청을 처리하여 특정 사용자 정보를 업데이트
app.put('/users/:id', (req, res) => {
  const userId = req.params.id;
  const updatedUser = req.body;
  console.log(`사용자 ${userId} 업데이트:`, updatedUser);
  res.send(`User ${userId} updated.`);
});

// DELETE 요청을 처리하여 특정 사용자 삭제
app.delete('/users/:id', (req, res) => {
    const userId = req.params.id;
    console.log(`사용자 ${userId} 삭제`);
    res.send(`User ${userId} deleted.`);
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

body-parser 미들웨어를 사용하여 POST, PUT 요청의 본문을 파싱합니다. app.use(bodyParser.json())을 통해 JSON 형식의 요청 본문을 파싱할 수 있습니다. POST 요청 시에는 새로운 사용자 정보를 생성하고, PUT 요청 시에는 특정 사용자 정보를 업데이트하며, DELETE 요청 시에는 특정 사용자 정보를 삭제합니다.

3.4. 추가 예제: 라우트 체이닝 및 파라미터 처리

다음은 라우트 체이닝과 복잡한 파라미터 처리를 보여주는 예제입니다.

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

// 라우트 체이닝
app.route('/articles/:articleId')
  .get((req, res) => {
    const articleId = req.params.articleId;
    res.send(`Article ${articleId} information`);
  })
  .put((req, res) => {
     const articleId = req.params.articleId;
      res.send(`Article ${articleId} updated`);
  })
  .delete((req, res) => {
    const articleId = req.params.articleId;
    res.send(`Article ${articleId} deleted`);
  });

app.get('/search', (req, res) => {
   const query = req.query;
   res.json({ query: query});
})

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

app.route()를 사용하여 여러 HTTP 메서드를 한 번에 처리하고, req.query를 사용하여 쿼리 파라미터를 객체 형태로 처리합니다.

4. 미들웨어 (Middleware)

4.1. 미들웨어의 역할

미들웨어는 Express.js에서 요청과 응답 사이에서 실행되는 함수입니다. 로깅, 인증, 데이터 유효성 검사 등 다양한 작업을 수행할 수 있으며, app.use() 메서드를 사용하여 등록합니다. 실행 순서가 중요합니다.

4.2. 전역 미들웨어

모든 요청에 대해 실행되는 미들웨어입니다. 다음은 모든 요청의 로그를 남기는 미들웨어 예제입니다.

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

app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
});

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

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

이 코드를 실행하면 모든 요청에 대한 로그가 콘솔에 출력됩니다. next() 함수를 호출해야 다음 미들웨어나 라우터 핸들러로 이동합니다.

4.3. 라우트별 미들웨어

특정 라우트에만 적용되는 미들웨어입니다. 다음은 특정 경로에 접근하는 사용자 인증 로직을 미들웨어로 구현한 예제입니다.

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

function authenticateUser(req, res, next) {
    const isLoggedIn = true;
    if (isLoggedIn) {
        next();
    } else {
        res.status(401).send('Unauthorized');
    }
}

app.get('/admin', authenticateUser, (req, res) => {
    res.send('Welcome to admin page!');
});

app.get('/', (req, res) => {
    res.send('Welcome to homepage!');
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

/admin 경로에 접근할 때 authenticateUser 미들웨어가 실행되어 사용자를 인증하고, 인증에 실패하면 401 에러를 응답합니다.

4.4. 에러 처리 미들웨어

에러 처리 미들웨어는 네 개의 인자 (err, req, res, next)를 받으며, 다른 미들웨어에서 발생한 에러를 처리합니다.

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

app.get('/error', (req, res, next) => {
  try {
    throw new Error('This is a test error');
  } catch(err){
    next(err)
  }
});

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('서버에 오류가 발생했습니다.');
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

/error 경로에서 고의로 에러를 발생시키고, 에러 처리 미들웨어가 이를 캐치하여 서버 오류 메시지를 클라이언트에게 전달합니다.

4.5. 추가 예제: 서드파티 미들웨어 활용

다음은 morgan, cors와 같은 서드파티 미들웨어를 사용하여 로깅 및 CORS 설정을 하는 예제입니다.

const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
const PORT = 3000;

app.use(morgan('dev'));
app.use(cors());

app.get('/', (req, res) => {
  res.send('Hello from third-party middleware example!');
});

app.get('/api/data', (req, res) => {
  res.json({ message: 'Data from the API', status: 'ok'});
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

morgan을 사용하여 HTTP 요청을 로깅하고, cors를 사용하여 다른 도메인에서 오는 요청을 처리합니다. npm install morgan cors 명령어로 해당 미들웨어를 설치해야 합니다.

5. 템플릿 엔진

5.1. 템플릿 엔진이란?

템플릿 엔진은 서버 측에서 데이터를 받아와 HTML 페이지를 동적으로 생성하는 데 사용됩니다. Express.js는 다양한 템플릿 엔진을 지원하며, 여기서는 EJS(Embedded JavaScript)를 사용합니다.

5.2. EJS 설치 및 설정

npm install ejs

Express.js 앱에서 EJS를 사용하려면 다음 코드를 추가합니다.

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

app.set('view engine', 'ejs');
app.set('views', './views');

5.3. 템플릿 파일 작성

views 디렉토리 안에 index.ejs 파일을 생성하고 다음과 같이 작성합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title><%= title %></title>
</head>
<body>
    <h1><%= message %></h1>
    <ul>
      <% items.forEach((item) => { %>
        <li><%= item.name %></li>
      <% }); %>
    </ul>
</body>
</html>

5.4. 라우터에서 렌더링

라우터에서 템플릿 파일을 렌더링하고 데이터를 전달하는 방법은 다음과 같습니다.

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

app.set('view engine', 'ejs');
app.set('views', './views');

app.get('/', (req, res) => {
    const data = {
      title: "Express.js EJS Template",
      message: "환영합니다!",
      items: [{name: 'apple'}, {name: 'banana'}, {name: 'cherry'}]
     };
    res.render('index', data);
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

/ 경로로 접속하면 index.ejs 파일이 렌더링되어 브라우저에 표시됩니다.

5.5. 추가 예제: 템플릿 상속 및 include

다음은 템플릿 상속 및 include를 사용하는 예제입니다.

views/layout.ejs 파일:

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>
    <header>
      <nav>
        <a href="/">Home</a> | <a href="/about">About</a>
      </nav>
    </header>
    <main>
      <%- body %>
    </main>
    <footer>
      <p>Copyright &copy; 2024</p>
    </footer>
</body>
</html>

views/home.ejs 파일:

<%- include('partials/head') %>

<h1>Home Page</h1>
<p>Welcome to the home page!</p>

<%- include('partials/foot') %>

views/about.ejs 파일:

<%- include('partials/head') %>

<h1>About Page</h1>
<p>This is the about page.</p>
<%- include('partials/foot') %>

views/partials/head.ejs 파일:

<h2>Partial Head</h2>

views/partials/foot.ejs 파일:

<h2>Partial Foot</h2>

server.js 파일:

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

app.set('view engine', 'ejs');
app.set('views', './views');
app.use(express.static('public'))

app.get('/', (req, res) => {
  res.render('layout', {
        title: "Home Page",
        body: "<%- include('home') %>"
      });
});

app.get('/about', (req, res) => {
    res.render('layout', {
      title: "About Page",
      body: "<%- include('about') %>"
    });
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

layout.ejs 파일에 기본 레이아웃을 설정하고, home.ejs, about.ejs 파일에서 해당 레이아웃을 상속받습니다. 또한 partials 디렉토리의 head.ejsfoot.ejs 파일을 include하여 코드 재사용성을 높입니다. public 디렉토리에 styles.css 파일을 만들어 스타일을 적용할 수 있습니다.

결론

Express.js는 Node.js 기반 웹 개발에서 강력한 도구입니다. 이 포스트에서는 Express.js의 기본 개념부터 라우팅, 미들웨어, 템플릿 엔진 사용까지 자세히 살펴보았습니다. 다양한 예제 코드를 통해 각 개념을 더 깊이 있게 이해할 수 있도록 하였습니다. 이러한 지식을 바탕으로 다양한 웹 애플리케이션과 API를 효율적으로 개발할 수 있을 것입니다. Express.js의 더 많은 기능을 익히고 활용하여 웹 개발 능력을 향상시켜 보세요.

728x90