Express.js 기반 백엔드 API 개발 핵심 가이드

프로젝트 설정 및 서버 실행

먼저 프로젝트 루트 디렉토리에 의존성을 추가해야 합니다. NPM 명령어를 통해 Express 프레임워크를 설치할 수 있습니다.

npm install express --save

이후 모듈을 불러와 인스턴스를 생성하고 HTTP 서버를 구동하는 기본적인 흐름은 다음과 같습니다. 포트 번호는 3000 대신 환경 변수를 활용하여 관리하는 것이 일반적인 관행입니다.

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

// 응답 메서드별 차이 확인
app.get('/start', (req, res) => {
  // 수동으로 헤더 설정 후 엔딩
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end('시작 완료');

  // 자동 헤더 처리 및 데이터 변환 (JSON 또는 문자열)
  app.get('/default', (req, res) => {
    res.send('기본 응답');
    
    // 자동으로 Content-Type 을 application/json 으로 변경
    res.json({ status: 'ok', code: 200 });

    // 템플릿 엔진 렌더링 시뮬레이션
    // res.render('view_name');
  });
});

app.listen(PORT, () => {
  console.log(`서버 구동 중 ${PORT} 번 포트`);
});

라우팅 및 URL 패턴 매칭

Express 는 URL 의 구조에 따라 유연한 라우팅을 지원합니다. 정규 표현식이나 파라미터를 활용해 동적인 경로를 정의할 수 있습니다.

// 옵션 플래그 매칭 (?) 
// 예: /home 또는 /home1
app.get('/users/:name?', (req, res) => {
  res.send('옵션 매칭 성공');
});

// 그룹 및 반복 (*) , (++)
// b 가 없거나 여러 번 등장 가능
app.get('/abc*de', (req, res) => {
  res.status(200).json({ msg: '반복 패턴 테스트' });
});

// URL 파라미터 추출 (:paramName)
// http://localhost:8080/catalog/product/9527
app.get('/product/:sku', (req, res) => {
  const productId = req.params.sku;
  res.json({ id: productId, category: 'electronics' });
});

// 명시적 정규표현식 사용
// 'f' 로 끝나는 경로 모두 일치
app.get(/.*f$/, (req, res) => {
  res.send('끝 글자 f 검증 완료');
});

미들웨어 처리 로직

요청 사이클에서 특정 작업을 순차적으로 수행하려면 미들웨어를 사용합니다. `next()` 함수를 호출하지 않으면 이후의 로직이 차단됩니다.

// 단일 경로 적용형 미들웨어 체인
app.post('/api/action', (req, res, next) => {
  // 첫 번째 단계: 인증 토큰 검증
  const tokenValid = true; 
  if (!tokenValid) return res.send('인증 실패');
  
  // 다음 미들웨어로 제어권 전달
  next();
}, (req, res) => {
  // 두 번째 단계: DB 쿼리 실행
  const dbResult = { result: 'database fetched' };
  res.json(dbResult);
});

배열 형태로 미들웨어를 정의하여 재사용하거나 분리하는 것도 가능합니다. 이전 미들웨어에서 응답 객체에 데이터를 저장하면 다음 단계에서 참조할 수 있습니다.

const authCheck = (req, res, next) => {
  console.log('로그인 여부 체크 진행 중');
  // 세션 정보 저장 예시
  res.locals.userStatus = 'logged_in';
  next();
};

const fetchData = (req, res) => {
  const userStatus = res.locals.userStatus;
  res.send(`사용자 상태: ${userStatus}`);
};

app.get('/secured-route', [authCheck], fetchData);

애플리케이션 및 라우터 미들웨어 범위

전역으로 실행되는 `app.use()`와 라우터별로 격리된 `router.use()`를 구분하여 사용하는 것이 중요합니다.

// 전역 적용 (모든 경로 호출 시 실행)
app.use((req, res, next) => {
  console.log('[Global] 모든 요청 intercepted');
  next();
});

// 특정 경로 하위에만 적용
app.use('/admin', (req, res, next) => {
  console.log('[Admin Only] 관리자 영역 진입 감지');
  next();
});

// 외부 라우터 모듈 연동
const adminRouter = require('./routes/adminRoute');
app.use('/manage', adminRouter);

// 파일 내 정의 예시 (adminRoute.js)
/*
const router = express.Router();
router.get('/', (req, res) => res.send('Admin Dashboard'));
module.exports = router;
*/

오류 처리 및 비동기 대응

예외 상황이 발생했거나 존재하지 않는 경로 접근 시 처리하기 위해 최종 미들웨어를 배치합니다. 특히 404 페이지 핸들러나 에러 전용 미들웨어 함수 signature 를 주의해야 합니다.

// 존재하지 않는 경로 처리 (최하단 위치)
app.use((req, res) => {
  res.status(404).json({ message: '요청하신 리소스를 찾을 수 없습니다.' });
});

// 에러 핸들러 전용 (4 개의 인자를 가짐)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Internal Server Error' });
});

입력 데이터 파싱 및 정적 파일 제공

POST 요청 본문이나 쿼리 파라미터를 가져올 때, Express 내부 모듈이나 라이브러리를 사용해 내용을 변환해 주어야 합니다.

// 폼 데이터 파싱 (application/x-www-form-urlencoded)
app.use(express.urlencoded({ extended: true }));
// JSON 본문 파싱 (application/json)
app.use(express.json());

app.post('/form-submit', (req, res) => {
  // req.body 객체를 통해 데이터 접근
  const { username, password } = req.query;
  res.send('데이터 수신 완료: ' + JSON.stringify(req.body));
});

이미지, CSS, JS 와 같은 정적 파일을 직접 배포할 때는 `express.static` 미들웨어를 활용합니다. 폴더 경로를 URL 앞부분에 명시할 수도 있습니다.

// './public' 폴더의 내용들을 '/resources' 경로로 매핑
app.use('/assets', express.static('./assets_folder'));

// 이때 접속 주소: http://localhost:8080/assets/main.css

태그: expressjs nodejs middleware-routing request-response-handling json-parsing

8월 29일 20:53에 게시됨