프로젝트 설정 및 서버 실행
먼저 프로젝트 루트 디렉토리에 의존성을 추가해야 합니다. 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