Express 개발 강좌, 에러 핸들링 및 응답 구조 설계

웹 애플리케이션을 개발할 때는 에러 핸들링과 응답 구조 설계가 매우 중요합니다. 사용자가 불필요한 오류 메시지에 혼란을 느끼지 않도록 하고, 발생하는 모든 문제를 일관되게 처리할 수 있어야 합니다. 본 강좌에서는 Express.js를 이용한 에러 핸들링과 응답 구조 설계에 대하여 상세히 설명하겠습니다.

1. Express.js 소개

Express.js는 Node.js를 위한 웹 애플리케이션 프레임워크로, 빠르고 간단하게 웹 서버를 구축할 수 있도록 설계되었습니다. RESTful API, 웹 서버, 대규모 웹 애플리케이션 등을 개발할 때 널리 사용되며, ORM과 DB와의 통합, 미들웨어와의 결합 등 유연한 구조를 지원합니다.

2. 에러 핸들링의 중요성

에러 핸들링은 애플리케이션의 안정성과 사용자 경험에 큰 영향을 미칩니다. 예상치 못한 오류가 발생했을 때, 사용자에게 적절한 피드백을 제공하고 시스템의 안전한 동작을 보장해야 합니다. 적절한 에러 핸들링이 이루어지지 않으면, 사용자는 혼란을 느끼고 불만을 가질 수 있습니다.

2.1. 에러 종류

애플리케이션에서 발생할 수 있는 오류는 크게 다음과 같이 나눌 수 있습니다:

  • 사용자 입력 오류: 사용자가 잘못된 데이터를 입력했을 경우
  • 서버 오류: 서버에서 발생하는 예기치 못한 오류
  • 네트워크 오류: 외부 API 호출이나 데이터베이스 연결에서 발생하는 오류

3. Express에서의 에러 핸들링

Express에서는 미들웨어를 통해 에러를 처리할 수 있습니다. 에러 핸들링 미들웨어는 일반적인 미들웨어와 유사하지만, 추가적인 매개변수로 에러 객체를 받아들입니다. 기본적인 에러 핸들러의 구조는 다음과 같습니다.


    app.use((err, req, res, next) => {
        // 에러 처리 로직
        res.status(err.status || 500);
        res.json({ error: err.message });
    });
    

3.1. 기본 에러 핸들링

다음은 기본적인 에러 핸들링의 예시입니다. 사용자가 로그인할 때, 인증 정보가 유효하지 않으면 에러 메시지를 클라이언트에게 반환합니다.


    app.post('/login', (req, res, next) => {
        const { username, password } = req.body;
        if (!username || !password) {
            const error = new Error('사용자 이름과 비밀번호를 입력하세요.');
            error.status = 400;
            return next(error);
        }

        // ... 인증 로직 ...
        const isAuthenticated = false; // 예시로 잘못된 인증 결과
        if (!isAuthenticated) {
            const error = new Error('인증 정보가 올바르지 않습니다.');
            error.status = 401;
            return next(error);
        }

        res.send('로그인 성공');
    });
    

4. 사용자 정의 에러 객체

Express에서는 에러 객체를 커스터마이즈하여 발생하는 에러의 유형을 분류할 수 있습니다. 예를 들어, 네트워크 오류나 데이터베이스 오류를 표현하기 위해 다음과 같이 사용자 정의 에러 객체를 생성할 수 있습니다.


    class CustomError extends Error {
        constructor(message, status) {
            super(message);
            this.status = status;
        }
    }

    app.get('/data', (req, res, next) => {
        // 데이터베이스 연결 오류 발생
        const error = new CustomError('데이터베이스 연결 실패', 500);
        return next(error);
    });
    

4.1. 사용자 정의 에러 핸들러

사용자 정의 에러 객체를 활용하여 에러 핸들링 로직을 더욱 세분화할 수 있습니다.


    app.use((err, req, res, next) => {
        if (err instanceof CustomError) {
            res.status(err.status).json({ message: err.message });
        } else {
            res.status(500).json({ message: '서버 오류' });
        }
    });
    

5. 응답 구조 설계

클라이언트와 서버 간의 일관된 데이터 구조를 유지하는 것은 매우 중요합니다. 일반적으로 JSON 형식으로 데이터를 송신하며, 응답 구조는 다음과 같이 설계할 수 있습니다:


    {
        "status": "success",
        "data": { /* 관련 데이터 */ },
        "message": "성공적으로 처리되었습니다."
    }
    

5.1. 표준화된 응답 구조

모든 API 응답에 대해 표준화된 구조를 제공하는 것이 좋습니다. 예를 들어, 성공적인 결과와 에러 결과를 모두 동일한 형태로 반환하도록 설정할 수 있습니다.


    const sendResponse = (res, status, data, message) => {
        res.status(status).json({
            status: status >= 400 ? 'error' : 'success',
            data: data || null,
            message: message || ''
        });
    };

    app.get('/items', (req, res) => {
        // 데이터 조회
        const items = [{ id: 1, name: 'item1' }];
        sendResponse(res, 200, items, '아이템 목록 조회 성공');
    });
    

6. 에러 로그 기록

에러가 발생할 경우 이를 로그로 기록하는 것은 매우 중요합니다. 에러를 추적하고, 문제를 신속하게 해결할 수 있도록 돕습니다. Winston과 같은 로깅 라이브러리를 사용할 수 있습니다.


    const winston = require('winston');
    
    const logger = winston.createLogger({
        level: 'error',
        format: winston.format.json(),
        transports: [
            new winston.transports.File({ filename: 'error.log' })
        ],
    });

    app.use((err, req, res, next) => {
        logger.error(err.message);
        res.status(err.status || 500).json({ message: '서버 오류' });
    });
    

7. 결론

에러 핸들링과 응답 구조 설계는 안정적이고, 사용자 친화적인 웹 애플리케이션을 개발하는 데 필수적입니다. Express.js의 유연한 미들웨어 기능을 활용하여 다양한 유형의 에러를 효과적으로 처리하고, 일관된 데이터 구조를 적용하면 개발 효율성을 높일 수 있습니다.

본 강좌가 여러분의 Express 개발 실력을 한 단계 끌어올리는 데 도움이 되었기를 바랍니다. 다음 강좌에서는 더 심화된 내용을 다뤄보겠습니다.

Express 개발 강좌, 기본 라우팅 설정 및 GET, POST 요청 처리

Express.js는 Node.js에서 가장 널리 사용되는 웹 애플리케이션 프레임워크로서,
서버 사이드 웹 개발을 단순화하고, 엔드포인트를 구성하여 빠르고 쉽게 웹 애플리케이션을 구축할 수 있게 해줍니다.
이번 강좌에서는 Express의 기본적인 라우팅 설정 방법과 GET 및 POST 요청을 처리하는 방법에 대해 자세히 살펴보겠습니다.

1. Express.js란?

Express.js는 Node.js 기반의 어플리케이션 프레임워크로, RESTful API와 웹 애플리케이션을 구축하는 데 도움을 줍니다.
Express는 경량화된 구조를 가지고 있어 빠르게 웹 서버를 설정하고, 미들웨어 기능을 통해 HTTP 요청을 보다 손쉽게 처리할 수 있습니다.
또한, 다양한 플러그인과 미들웨어를 통한 확장성이 뛰어납니다.

2. Express 설치 및 기본 설정

Express를 사용하기 위해서는 Node.js가 필요합니다. Node.js가 설치된 후, Express를 설치하는 방법을 살펴보겠습니다.

npm install express

설치가 완료되면, 다음과 같이 Express 애플리케이션을 설정할 수 있습니다.

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

app.listen(PORT, () => {
    console.log(`서버가 포트 ${PORT}에서 실행되고 있습니다.`);
});

3. 기본 라우팅 설정

Express 애플리케이션에서 라우팅은 HTTP 요청을 특정 URL 경로와 연결하는 과정입니다.
기본적인 라우팅을 설정하는 방법을 살펴보겠습니다.

3.1 기본 GET 요청 처리

GET 요청은 서버에서 데이터를 요청할 때 사용됩니다. 다음과 같이 기본 경로(“/”)에 대한 GET 요청을 처리하는 라우트를 설정할 수 있습니다.

app.get('/', (req, res) => {
    res.send('안녕하세요, Express!');
});

위 코드를 실행한 후 브라우저에서 http://localhost:3000를 방문하면
“안녕하세요, Express!”라는 메시지를 확인할 수 있습니다.

3.2 GET 요청의 URL 매개변수 처리

URL 매개변수는 동적인 경로를 정의할 수 있게 해줍니다. 예를 들어, 사용자 ID를 URL로 전달받는 방법을 알아보겠습니다.

app.get('/user/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`사용자 ID는 ${userId}입니다.`);
});

이를 통해 http://localhost:3000/user/123 라는 URL로 접속하면
“사용자 ID는 123입니다.”라는 메시지를 표시합니다.

3.3 기본 POST 요청 처리

POST 요청은 서버에 데이터를 전송할 때 사용됩니다. Express에서는 POST 요청을 다음과 같이 처리할 수 있습니다.

app.use(express.json()); // JSON 형식의 바디를 파싱하기 위한 미들웨어

app.post('/user', (req, res) => {
    const user = req.body; // JSON 바디에서 사용자 데이터 추출
    res.send(`사용자가 추가되었습니다: ${JSON.stringify(user)}`);
});

위 코드는 사용자의 정보를 JSON 형식으로 POST 요청을 통해 받을 준비를 하고 있습니다.
이 API 호출을 테스트하기 위해 Postman과 같은 도구를 사용할 수 있습니다.

4. GET 및 POST 요청 예제

아래는 GET 및 POST 요청을 처리하는 간단한 예제입니다. 여기에 사용자 정보를 저장하고 검색하는 기능을 추가해보겠습니다.

let users = []; // 사용자 정보를 저장할 배열

// GET 요청: 모든 사용자 정보 조회
app.get('/users', (req, res) => {
    res.json(users);
});

// POST 요청: 신규 사용자 추가
app.post('/users', (req, res) => {
    const newUser = req.body;
    users.push(newUser);
    res.status(201).send(`사용자 ${newUser.name} 추가 성공`);
});

이 API는 사용자의 추가와 조회 기능을 제공하며, 사용자는 JSON 형식으로 데이터를 전송할 수 있습니다. 예를 들어,
다음과 같이 요청을 보내면 새로운 사용자를 추가할 수 있습니다.

{ "name": "홍길동", "age": 30 }

5. 미들웨어 사용

Express의 강력한 기능 중 하나는 미들웨어를 사용하여 요청과 응답 객체를 수정하거나 요청을 처리하는 것입니다.
미들웨어는 요청이 라우터에 도달하기 전에 또는 응답이 클라이언트에 도달하기 전에 실행됩니다.

5.1 기본 미들웨어 설정

app.use(express.json()); // JSON 데이터 파싱 미들웨어
app.use(express.urlencoded({ extended: true })); // URL-encoded 데이터 파싱 미들웨어 

5.2 사용자 정의 미들웨어

사용자 정의 미들웨어를 만들어 요청의 로깅을 수행할 수 있습니다.

app.use((req, res, next) => {
    console.log(`${req.method} 요청에 대한 ${req.url}에 접근했습니다.`);
    next(); // 다음 미들웨어 또는 라우터로 제어를 전달
});

6. 오류 처리

오류 처리는 모든 서버 애플리케이션에서 매우 중요합니다. Express에서는 오류 처리를 위해 특별한 미들웨어를 사용할 수 있습니다.

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('서버 내부 오류 발생');
});

위 미들웨어는 서버에서 발생한 오류를 청구하고, 클라이언트에게 오류 메시지를 전송합니다.

7. 마무리

이번 강좌에서는 Express를 사용하여 기본적인 라우팅을 설정하고, GET 및 POST 요청을 처리하는 방법에 대해 설명했습니다.
Express는 이처럼 간단한 설정으로 강력한 서버를 구성할 수 있도록 도와줍니다.
다음 강좌에서는 데이터베이스와의 연동 및 좀 더 복잡한 기능을 다룰 예정이니 많은 관심 부탁드립니다.

감사합니다!

Express 개발 강좌, 단위 테스트 및 통합 테스트를 위한 Mocha, Chai 사용하기

웹 애플리케이션 개발에서 테스트는 필수적인 과정입니다. 테스트는 코드의 신뢰성을 확보하고, 버그를 조기에 발견하며, 기능이 의도한 대로 동작하는지를 검증하는 데 중요한 역할을 합니다. 특히 Express.js와 같은 서버측 애플리케이션 개발에서는 Mocha와 Chai와 같은 도구를 통해 단위 테스트 및 통합 테스트를 쉽게 수행할 수 있습니다. 본 강좌에서는 이러한 도구들을 활용하여 Express 애플리케이션의 테스트를 작성하는 방법에 대해 설명합니다.

1. Mocha와 Chai 소개

Mocha는 Node.js와 브라우저에서 JavaScript 테스트를 수행하기 위한 유연한 테스트 프레임워크입니다. 비동기 테스트 실행을 지원하며, 다양한 보고서(output) 형식을 제공하여 테스트 결과를 쉽게 확인할 수 있습니다. Chai는 Mocha와 함께 사용할 수 있는 assertion 라이브러리로, 테스트에서 기대하는 결과를 검증하는 다양한 문법을 제공합니다. 이 두 도구는 함께 사용될 때 서로의 장점을 살려주며, 효율적인 테스트 환경을 제공합니다.

2. Express 애플리케이션 세팅하기

먼저 Express 애플리케이션을 만들기 위해 기본적인 환경을 설정해 보겠습니다. 다음은 Express 서버를 설정하는 예제 코드입니다.


const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

// 기본 라우트
app.get('/', (req, res) => {
    res.send('Hello World!');
});

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

3. Mocha 및 Chai 설치하기

프로젝트에 Mocha와 Chai를 추가하기 위해 다음의 명령어를 사용하여 설치합니다.


npm install --save-dev mocha chai

설치가 완료되면, package.json 파일을 열어 scripts 섹션에 Mocha를 실행할 수 있는 명령어를 추가합니다.


"scripts": {
    "test": "mocha"
}

4. 단위 테스트 작성하기

이제 단위 테스트를 작성해보겠습니다. 단위 테스트는 개별적인 함수나 모듈을 독립적으로 검증하는 테스트입니다. 예를 들어, 위에서 작성한 Express 앱의 라우트 핸들러를 테스트 해보겠습니다.


const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../app'); // 여러분의 Express 앱을 불러옵니다.
const should = chai.should();

chai.use(chaiHttp);

describe('GET /', () => {
    it('기본 페이지를 요청할 때 200 상태코드와 응답을 확인합니다', (done) => {
        chai.request(app)
            .get('/')
            .end((err, res) => {
                res.should.have.status(200);
                res.text.should.be.eql('Hello World!');
                done();
            });
    });
});

위의 코드에서 chai-http 미들웨어를 사용하여 HTTP 요청을 테스트하는 방법을 볼 수 있습니다. describeit 메소드는 Mocha의 구조로, 각 테스트의 기능과 유닛을 명명합니다.

5. 통합 테스트 작성하기

통합 테스트는 여러 모듈이나 서브시스템이 함께 잘 작동하는지를 검증하는 테스트입니다. 아래는 Express 애플리케이션의 CRUD(Create, Read, Update, Delete) 기능을 테스트하는 간단한 예제입니다.


const mongoose = require('mongoose');
const { app, closeDatabase, clearDatabase } = require('../app');
const chai = require('chai');
const chaiHttp = require('chai-http');
const UserModel = require('../models/User'); // 사용자 모델을 불러옵니다.

chai.use(chaiHttp);

afterEach(async () => {
    await clearDatabase(); // 각 테스트 후 DB를 초기화합니다.
});

after(async () => {
    await closeDatabase(); // 모든 테스트 완료 후 DB 연결 종료
});

describe('Users API', () => {
    it('사용자를 생성합니다', (done) => {
        const user = {
            name: 'John Doe',
            email: 'john@example.com'
        };
        
        chai.request(app)
            .post('/users')
            .send(user)
            .end((err, res) => {
                res.should.have.status(201);
                res.body.should.have.property('name').eql('John Doe');
                done();
            });
    });

    it('모든 사용자를 조회합니다', (done) => {
        chai.request(app)
            .get('/users')
            .end((err, res) => {
                res.should.have.status(200);
                res.body.should.be.a('array');
                done();
            });
    });
});

6. Mocking과 Stubbing을 통한 테스트 개선하기

테스트를 더욱 효과적으로 만들기 위해, Mocking과 Stubbing 기법을 사용할 수 있습니다. 이는 외부 종속성을 제거하고, 테스트의 신뢰성을 높이는데 도움이 됩니다. Sinon.js 라이브러리를 사용하여 구현할 수 있습니다.


const sinon = require('sinon');
const UserService = require('../services/UserService');

describe('UserService', () => {
    it('사용자가 없을 경우 빈 배열을 반환한다', () => {
        const userService = sinon.stub(UserService, "getAllUsers").returns([]);

        const result = userService();
        result.should.be.a('array').that.is.empty;

        userService.restore(); // 스텁 복원
    });
});

7. 테스트 실행하기

모든 테스트를 작성하였다면, 다음 커맨드를 통해 테스트를 실행합니다.


npm test

성공적으로 테스트가 통과하면, 여러분의 코드는 정상적으로 작동하는 것입니다. 이를 통해 코드 변경 시에도 애플리케이션의 안정성을 유지할 수 있습니다.

8. 테스트 커버리지 확인하기

테스트 커버리지는 테스트가 얼마나 많은 코드 라인을 실행하는지를 측정하는 지표입니다. Istanbul과 같은 도구를 사용하여 커버리지를 확인할 수 있습니다. 커버리지 레포트는 코드의 미비한 부분을 찾아내는 데 유용합니다.


npm install --save-dev nyc

테스트 명령어를 업데이트하여 커버리지 리포트를 생성할 수 있도록 합니다.


"scripts": {
    "test": "nyc mocha"
}

이제 다음 명령어로 커버리지 리포트를 확인할 수 있습니다.


npm test

9. CI/CD와 자동화

프로젝트에 CI/CD(지속적 통합 및 지속적 배포)를 적용하여 테스트를 자동화하는 것이 좋습니다. GitHub Actions, Travis CI, CircleCI 등과 같은 도구를 활용하여, 코드 변경 시 자동으로 테스트를 실행하고, 결과를 확인할 수 있습니다. 이는 팀의 협업을 더욱 원활하게 하고, 지속적인 코드 품질 향상에 기여합니다.

10. 마무리

이번 강좌에서는 Express 백엔드 서버 개발에 있어 Mocha와 Chai를 활용한 단위 테스트 및 통합 테스트에 대해 알아보았습니다. 테스트는 애플리케이션의 신뢰성을 높이고 유지보수를 쉬워지게 만들어줍니다. 코드를 작성할 때 테스트를 미리 고려하는 습관을 들이면, 개발 과정의 상당 부분에서 품질을 보장할 수 있습니다. 앞으로의 개발에서도 테스트를 적극 활용하시길 바랍니다.

Express 개발 강좌, Node.js 및 Express.js 설치 방법

본 강좌에서는 Node.jsExpress.js를 설치하고 설정하는 방법에 대해 자세히 설명합니다. Node.js는 서버 사이드 자바스크립트 실행 환경으로, Express.js는 Node.js 위에서 동작하는 웹 애플리케이션 프레임워크로써, 웹 서버를 쉽게 만들 수 있도록 도와줍니다.

1. Node.js란?

Node.js는 구글 크롬의 V8 자바스크립트 엔진을 기반으로 하는 서버 사이드 플랫폼입니다. 비동기 이벤트 기반 아키텍처를 사용하여, 높은 성능의 네트워크 애플리케이션을 만들 수 있게 해줍니다. Node.js는 자바스크립트를 사용하여 서버 사이드에서 코드 작성이 가능하게 하여, 프론트엔드와 백엔드에 동일한 언어로 개발할 수 있는 장점을 제공합니다.

Node.js의 주요 특징

  • 비동기 I/O: Node.js는 비동기적으로 작업을 진행하여, 많은 수의 클라이언트 요청을 동시에 처리할 수 있습니다.
  • 단일 스레드 이벤트 루프: Node.js는 단일 스레드를 사용하여 이벤트 루프를 통해 요청을 처리합니다. 이로 인해 자원을 효율적으로 사용할 수 있습니다.
  • 풍부한 패키지 생태계: npm(Node Package Manager)을 통해 수많은 패키지를 쉽게 설치하고 관리할 수 있습니다.

2. Express.js란?

Express.js는 Node.js 위에서 구축된 웹 애플리케이션 프레임워크입니다. 매우 간결하고 유연한 구조를 가지고 있어 RESTful API 및 웹 애플리케이션을 쉽게 제작할 수 있도록 돕습니다. Express.js는 미들웨어를 중심으로 설계되어, 다양한 기능을 추가할 수 있는 확장성이 있습니다.

Express.js의 주요 특징

  • 간편한 라우팅: URL 경로에 따라 다른 핸들러를 지정할 수 있습니다.
  • 미들웨어 지원: 요청과 응답 사이에 로직을 추가할 수 있는 미들웨어를 쉽게 사용할 수 있습니다.
  • 설정 파일이 필요 없음: 프로젝트 설정을 위한 별도의 설정 파일이 필요 없어 즉시 개발을 시작할 수 있습니다.

3. Node.js 설치 방법

Node.js를 설치하기 위해서는 공식 홈페이지에서 설치 파일을 다운로드해야 합니다. 아래 단계에 따라 설치를 진행하세요.

3.1. Node.js 다운로드

1. [Node.js 공식 웹사이트](https://nodejs.org/)에 접속합니다.
2. LTS(Long Term Support) 버전과 Current 버전이 제공됩니다. 안정성을 우선시한다면 LTS 버전을 선택합니다.
3. 운영체제에 맞는 설치 파일을 다운로드합니다.

3.2. 설치하기

1. 다운로드한 설치 파일을 실행합니다.
2. 설치 마법사의 안내에 따라 진행합니다.
3. 설치가 완료되면 명령 프롬프트 또는 터미널을 열어 아래 명령어를 입력하여 설치가 성공적으로 이루어졌는지 확인합니다.

node -v

위 명령어를 입력하면 Node.js의 버전이 출력되어야 합니다. 만약 버전이 출력되면 성공적으로 설치된 것입니다.

4. npm(Node Package Manager) 설치하기

Node.js를 설치하면 기본적으로 npm도 함께 설치됩니다. npm은 Node.js 패키지 관리자로, 패키지를 설치하고 관리하는 데 사용됩니다. 아래 명령어로 npm이 정상적으로 설치되었는지 확인할 수 있습니다.

npm -v

npm의 버전이 출력되면 정상적으로 설치된 것입니다. npm을 통해 Express를 설치하기 전에, 먼저 패키지의 최신 목록을 업데이트할 수 있습니다.

5. Express.js 설치 방법

이제 Node.js와 npm이 설치되었으니, Express.js를 설치할 준비가 되었습니다. 아래의 단계에 따라 Express.js를 설치할 수 있습니다.

5.1. 새로운 프로젝트 폴더 생성

작업할 폴더를 생성한 후 해당 폴더로 이동합니다. 아래 명령어를 사용해 새 폴더를 생성하고 그 안으로 이동합니다.

mkdir my-express-app && cd my-express-app

5.2. npm 초기화

아래의 명령어를 입력하여 npm 프로젝트를 초기화합니다.
이 과정에서 일부 정보를 입력 받게 되는데, 기본값으로 진행하고 싶다면 엔터 키를 누르면 됩니다.

npm init -y

5.3. Express.js 설치

Express를 설치하기 위해 아래 명령어를 입력합니다.

npm install express

설치가 완료되면 node_modules 폴더와 package.json 파일이 생성됩니다. package.json 파일은 프로젝트에 필요한 패키지와 버전 정보를 담고 있습니다.

6. 기본 Express 서버 생성하기

Express를 설치한 후, 간단한 서버를 만들어 볼 차례입니다. 아래의 코드를 사용하여 기본적인 Express 서버를 생성할 수 있습니다.

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

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

app.listen(PORT, () => {
    console.log(`서버가 http://localhost:${PORT}에서 작동중입니다.`);
});

코드 설명

  • const express = require('express');: Express 모듈을 불러옵니다.
  • const app = express();: Express 애플리케이션 객체를 생성합니다.
  • app.get('/', ...): 루트 경로(‘/’)로 들어오는 GET 요청에 대해 응답을 지정합니다.
  • app.listen(PORT, ...): 지정한 포트에서 서버를 실행합니다.

7. 서버 실행하기

작성한 코드를 server.js와 같은 파일로 저장한 후, 터미널에서 아래 명령어로 서버를 실행합니다.

node server.js

이제 웹 브라우저를 열고 http://localhost:3000에 접속하면 “Hello, Express!”라는 메시지를 볼 수 있습니다.

8. 추가적으로 알아두면 좋은 점

Express.js를 사용하면 RESTful API를 쉽게 구축할 수 있고, 다양한 미들웨어를 통해 기능을 확장할 수 있습니다. JSON 데이터 전송, 세션 관리, 인증 등 다양한 기능을 제공하므로, 다양한 웹 애플리케이션 구축에 유용하게 사용할 수 있습니다.

9. 마무리

이번 강좌에서는 Node.js와 Express.js의 설치 방법과 기본적인 서버 설정을 배웠습니다. 이 과정을 통해 더 다양한 기능을 탐구하고, 실제 애플리케이션을 개발하는 데 더 깊이 있게 들어갈 수 있습니다. 다음 강좌에서는 CRUD(생성, 읽기, 업데이트, 삭제) 기능을 가진 웹 애플리케이션을 만들어보겠습니다.

노트: Express.js와 Node.js의 공식 문서에서도 추가적인 정보와 예제를 찾아볼 수 있습니다. 이는 개발 중에 큰 도움이 될 것입니다.

Express 개발 강좌, 사용자 정의 미들웨어 만들기

Express는 Node.js를 기반으로 한 웹 애플리케이션 프레임워크로, 강력한 미들웨어 시스템을 제공합니다. 이 강좌에서는 Express의 미들웨어를 이해하고, 사용자 정의 미들웨어를 만드는 방법에 대해 자세히 설명하겠습니다. 사용자 정의 미들웨어는 애플리케이션의 요구 사항에 맞추어 기능을 확장할 수 있는 매우 유용한 도구입니다.

1. Express와 미들웨어의 이해

미들웨어는 요청(Request)과 응답(Response) 객체를 처리하는 함수입니다. Express에서는 미들웨어를 통해 요청을 가로채고 변경하거나, 요청에 대한 응답을 처리할 수 있습니다. 미들웨어는 다음과 같은 용도로 사용될 수 있습니다:

  • 요청 로그 기록
  • 요청의 인증 및 권한 부여
  • 요청 데이터 파싱
  • 정적 파일 제공
  • 오류 처리

1.1 미들웨어의 기본 구조

미들웨어 함수는 다음과 같은 기본 형식을 가집니다:

function middleware(req, res, next) {
    // 요청 처리 로직
    next(); // 다음 미들웨어로 이동
}

여기서:

  • req: 요청 객체로, 클라이언트의 요청 정보를 담고 있습니다.
  • res: 응답 객체로, 서버가 클라이언트에게 보낼 응답 정보를 담고 있습니다.
  • next: 다음 미들웨어로 제어를 넘기는 함수입니다. 이 함수를 호출하지 않으면 요청이 응답을 받지 못하고 대기 상태에 빠집니다.

2. 사용자 정의 미들웨어 만들기

사용자 정의 미들웨어는 애플리케이션의 특정 요구 사항을 충족하기 위해 개발자가 직접 작성하는 미들웨어입니다. 이번 섹션에서는 몇 가지 예제를 통해 사용자 정의 미들웨어를 만드는 방법을 알아보겠습니다.

2.1 기본 사용자 정의 미들웨어 예제

우선, 간단한 사용자 정의 미들웨어를 만들어 보겠습니다. 이 미들웨어는 모든 요청에 대해 콘솔에 로그를 남기는 역할을 합니다.

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

// 사용자 정의 미들웨어
function requestLogger(req, res, next) {
    console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
    next();
}

// 미들웨어 등록
app.use(requestLogger);

// 기본 경로
app.get('/', (req, res) => {
    res.send('Welcome to the Express App!');
});

// 서버 시작
app.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
});

위의 코드를 실행하면 모든 요청에 대해 요청 시간과 메서드, URL이 로그에 출력됩니다. app.use(requestLogger);를 통해 미들웨어를 등록하고, 미들웨어가 요청을 처리한 후 next();를 호출하여 다음 단계로 진행합니다.

2.2 요청 데이터 파싱 미들웨어

이번에는 요청 본문(body)을 파싱하는 사용자 정의 미들웨어를 만들어 보겠습니다. 이 미들웨어는 JSON 형식의 데이터를 받아서 요청 객체에 추가합니다.

function bodyParser(req, res, next) {
    let body = '';
    req.on('data', chunk => {
        body += chunk.toString(); // 데이터 조각을 문자열로 변환
    });
    req.on('end', () => {
        req.body = JSON.parse(body); // 요청 본문을 JSON으로 파싱
        next(); // 다음 미들웨어 또는 라우터로 이동
    });
}

// 미들웨어 등록
app.use(bodyParser);

// POST 경로
app.post('/data', (req, res) => {
    res.send(`Received data: ${JSON.stringify(req.body)}`);
});

위 코드에서는 req.on('data')req.on('end') 이벤트를 사용하여 요청 본문을 수집하고 JSON 형식으로 파싱합니다. 이 미들웨어를 사용하기 위해서 덧붙여 app.use(bodyParser);를 등록합니다.

2.3 인증 미들웨어

다음은 인증을 위한 미들웨어입니다. 사용자가 특정 경로에 접근할 때 유효한 토큰을 가진 경우에만 접근을 허용합니다.

function authMiddleware(req, res, next) {
    const token = req.headers['authorization'];
    
    if (token === 'valid_token') {
        next(); // 인증 성공, 다음 미들웨어로 이동
    } else {
        res.status(403).send('Forbidden: Invalid token'); // 인증 실패
    }
}

// 인증 미들웨어 등록
app.use('/protected', authMiddleware);

// 보호된 경로
app.get('/protected/data', (req, res) => {
    res.send('This is protected data');
});

인증 미들웨어에서는 요청 헤더에서 authorization 필드를 확인하고, 유효한 토큰이 아닐 경우 인증을 거부합니다. app.use('/protected', authMiddleware);를 통해 보호된 경로에 미들웨어를 적용합니다.

3. 에러 처리 미들웨어

Express에서는 에러 처리 미들웨어를 따로 구성하여 애플리케이션의 오류를 중앙에서 관리할 수 있습니다. 에러 처리 미들웨어는 네 개의 매개변수를 받아야 하며, 주로 마지막에 넣어주는 것이 좋습니다.

function errorHandler(err, req, res, next) {
    console.error(err.stack); // 에러 스택을 콘솔에 출력
    res.status(500).send('Something broke!'); // 사용자에게 에러 메시지 전송
}

// 에러 처리 미들웨어 등록
app.use(errorHandler);

위의 코드는 애플리케이션 내에서 발생한 모든 오류를 처리합니다. 오류가 발생하면 errorHandler 미들웨어가 호출되어 오류 로그를 출력하고 사용자에게 적절한 메시지를 보냅니다.

4. 사용자 정의 미들웨어의 활용

이제 몇 가지 사용자 정의 미들웨어를 만들었으니, 이들을 조합하여 더 복잡한 기능을 구현해 볼 수 있습니다.

4.1 미들웨어 체인

여러 미들웨어를 체이닝하여 요청 처리 흐름을 제어할 수 있습니다. 예를 들어, 인증과 요청 데이터를 함께 처리할 수 있습니다.

app.use(authMiddleware);
app.use(bodyParser);

app.post('/secure-data', (req, res) => {
    res.send(`Secure data received: ${JSON.stringify(req.body)}`);
});

위 코드는 /secure-data 경로에 대해 인증 미들웨어와 본문 파싱 미들웨어를 순차적으로 적용합니다. 인증이 성공한 후에만 요청 본문을 파싱하여 그 결과를 응답합니다.

5. 성능 최적화 및 주의사항

사용자 정의 미들웨어를 개발할 때 성능과 관련된 몇 가지 주의사항이 있습니다:

  • 미들웨어는 가능한 적게 사용하여 요청 처리 성능에 미치는 영향을 최소화해야 합니다.
  • 미들웨어가 요구된 작업을 수행하는 데 필요한 최소한의 로직만 포함되어야 합니다.
  • 소셜 미디어와 애플리케이션 간의 데이터 흐름을 제어하기 위해 불필요한 계산을 피해야 합니다.

5.1 비동기 처리

비동기 코드를 작성할 경우, async/await를 사용하여 미들웨어를 작성할 수 있습니다. 이럴 경우에는 next() 호출 대신 직접 오류를 throw하여 에러 처리 미들웨어에 전달할 수 있습니다.

async function asyncMiddleware(req, res, next) {
    try {
        // 비동기 작업 수행
        const result = await someAsyncFunction();
        req.data = result; // 결과를 요청 객체에 저장
        next();
    } catch (err) {
        next(err); // 오류를 에러 처리 미들웨어로 전달
    }
}

6. 결론

Express에서 사용자 정의 미들웨어를 만드는 것은 요청을 처리하고 기능을 확장하는 강력한 방법입니다. 이 강좌를 통해 기본적인 미들웨어 생성 방법, 비동기 처리, 인증, 에러 처리 등을 살펴보았습니다. 이러한 기술들을 결합하여 보다 복잡하고 유용한 웹 애플리케이션을 개발할 수 있습니다.

미들웨어는 애플리케이션의 구조를 깔끔하게 유지하면서도 필요한 기능을 쉽게 추가하는 데 도움을 줍니다. 여러분의 Express 애플리케이션에서 사용자 정의 미들웨어를 적극 활용하여 보다 나은 웹 서비스를 구축하시기 바랍니다.