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 개발 실력을 한 단계 끌어올리는 데 도움이 되었기를 바랍니다. 다음 강좌에서는 더 심화된 내용을 다뤄보겠습니다.