Express.js의 고급 미들웨어 패턴을 탐색하여 글로벌 사용자를 위한 강력하고 확장 가능하며 유지 관리 가능한 웹 애플리케이션을 구축하세요. 오류 처리, 인증, 속도 제한 등에 대해 알아보세요.
Express.js 미들웨어: 확장 가능한 애플리케이션을 위한 고급 패턴 마스터하기
Express.js는 Node.js를 위한 빠르고, 독단적이지 않으며, 최소한의 웹 프레임워크로 웹 애플리케이션 및 API 구축의 초석입니다. 그 핵심에는 강력한 미들웨어 개념이 있습니다. 이 블로그 게시물에서는 고급 미들웨어 패턴을 자세히 살펴보고 전 세계 사용자를 위한 강력하고 확장 가능하며 유지 관리 가능한 애플리케이션을 만드는 데 필요한 지식과 실제 예제를 제공합니다. 오류 처리, 인증, 권한 부여, 속도 제한 및 최신 웹 애플리케이션 구축의 기타 중요한 측면에 대한 기술을 살펴봅니다.
미들웨어 이해: 기초
Express.js의 미들웨어 함수는 애플리케이션의 요청-응답 주기에서 요청 객체(req
), 응답 객체(res
) 및 다음 미들웨어 함수에 액세스할 수 있는 함수입니다. 미들웨어 함수는 다음과 같은 다양한 작업을 수행할 수 있습니다.
- 모든 코드 실행.
- 요청 및 응답 객체 변경.
- 요청-응답 주기 종료.
- 스택에서 다음 미들웨어 함수 호출.
미들웨어는 기본적으로 파이프라인입니다. 각 미들웨어는 특정 기능을 수행한 다음 선택적으로 체인의 다음 미들웨어에 제어를 전달합니다. 이 모듈식 접근 방식은 코드 재사용, 관심사 분리 및 더 깔끔한 애플리케이션 아키텍처를 촉진합니다.
미들웨어의 구조
일반적인 미들웨어 함수는 다음과 같은 구조를 따릅니다.
function myMiddleware(req, res, next) {
// 작업 수행
// 예: 요청 정보 기록
console.log(`요청: ${req.method} ${req.url}`);
// 스택에서 다음 미들웨어 호출
next();
}
next()
함수는 매우 중요합니다. 현재 미들웨어가 작업을 완료했으며 제어를 다음 미들웨어 함수로 전달해야 함을 Express.js에 알립니다. next()
가 호출되지 않으면 요청이 중단되고 응답이 전송되지 않습니다.
미들웨어 유형
Express.js는 각각 고유한 목적을 수행하는 여러 유형의 미들웨어를 제공합니다.
- 애플리케이션 수준 미들웨어: 모든 경로 또는 특정 경로에 적용됩니다.
- 라우터 수준 미들웨어: 라우터 인스턴스 내에 정의된 경로에 적용됩니다.
- 오류 처리 미들웨어: 오류를 처리하도록 특별히 설계되었습니다. 미들웨어 스택에서 경로 정의 *후*에 배치됩니다.
- 내장 미들웨어: Express.js에서 포함합니다(예: 정적 파일 제공을 위한
express.static
). - 타사 미들웨어: npm 패키지에서 설치합니다(예: body-parser, cookie-parser).
고급 미들웨어 패턴
Express.js 애플리케이션의 기능, 보안 및 유지 관리 용이성을 크게 향상시킬 수 있는 몇 가지 고급 패턴을 살펴보겠습니다.
1. 오류 처리 미들웨어
효과적인 오류 처리는 안정적인 애플리케이션 구축에 가장 중요합니다. Express.js는 미들웨어 스택의 *마지막*에 배치되는 전용 오류 처리 미들웨어 함수를 제공합니다. 이 함수는 네 개의 인수 (err, req, res, next)
를 사용합니다.
다음은 예입니다.
// 오류 처리 미들웨어
app.use((err, req, res, next) => {
console.error(err.stack); // 디버깅을 위해 오류 기록
res.status(500).send('문제가 발생했습니다!'); // 적절한 상태 코드로 응답
});
오류 처리를 위한 주요 고려 사항:
- 오류 로깅: 로깅 라이브러리(예: Winston, Bunyan)를 사용하여 디버깅 및 모니터링을 위해 오류를 기록합니다. 다양한 심각도 수준(예:
error
,warn
,info
,debug
)을 기록하는 것을 고려하십시오. - 상태 코드: 클라이언트에 오류의 성격을 전달하기 위해 적절한 HTTP 상태 코드(예: 잘못된 요청에 대한 400, 권한이 없는 경우 401, 내부 서버 오류에 대한 500)를 반환합니다.
- 오류 메시지: 클라이언트에 유익하면서도 안전한 오류 메시지를 제공합니다. 응답에 민감한 정보를 노출하지 마십시오. 사용자에게 일반 메시지를 반환하는 동안 문제를 내부적으로 추적하기 위해 고유한 오류 코드를 사용하는 것을 고려하십시오.
- 중앙 집중식 오류 처리: 더 나은 구성 및 유지 관리 용이성을 위해 전용 미들웨어 함수에서 오류 처리를 그룹화합니다. 다양한 오류 시나리오에 대한 사용자 지정 오류 클래스를 만듭니다.
2. 인증 및 권한 부여 미들웨어
API를 보호하고 민감한 데이터를 보호하는 것이 중요합니다. 인증은 사용자의 신원을 확인하고 권한 부여는 사용자가 수행할 수 있는 작업을 결정합니다.
인증 전략:
- JSON 웹 토큰(JWT): API에 적합한 널리 사용되는 상태 비저장 인증 방법입니다. 서버는 로그인에 성공하면 클라이언트에 JWT를 발급합니다. 그러면 클라이언트는 후속 요청에 이 토큰을 포함합니다.
jsonwebtoken
과 같은 라이브러리가 일반적으로 사용됩니다. - 세션: 쿠키를 사용하여 사용자 세션을 유지 관리합니다. 이는 웹 애플리케이션에 적합하지만 JWT보다 확장성이 떨어질 수 있습니다.
express-session
과 같은 라이브러리는 세션 관리를 용이하게 합니다. - OAuth 2.0: 사용자가 자격 증명을 직접 공유하지 않고도 리소스에 대한 액세스 권한을 부여할 수 있도록 하는 널리 채택된 위임 권한 부여 표준입니다. (예: Google, Facebook 등으로 로그인). 특정 OAuth 전략과 함께
passport.js
와 같은 라이브러리를 사용하여 OAuth 흐름을 구현합니다.
권한 부여 전략:
- 역할 기반 액세스 제어(RBAC): 사용자에게 역할(예: 관리자, 편집자, 사용자)을 할당하고 이러한 역할을 기반으로 권한을 부여합니다.
- 속성 기반 액세스 제어(ABAC): 사용자, 리소스 및 환경의 속성을 사용하여 액세스를 결정하는 보다 유연한 접근 방식입니다.
예(JWT 인증):
const jwt = require('jsonwebtoken');
const secretKey = 'YOUR_SECRET_KEY'; // 강력한 환경 변수 기반 키로 바꿉니다.
// JWT 토큰을 확인하는 미들웨어
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401); // 권한 없음
jwt.verify(token, secretKey, (err, user) => {
if (err) return res.sendStatus(403); // 금지됨
req.user = user; // 사용자 데이터를 요청에 첨부
next();
});
}
// 인증으로 보호되는 예제 경로
app.get('/profile', authenticateToken, (req, res) => {
res.json({ message: `환영합니다, ${req.user.username}` });
});
중요한 보안 고려 사항:
- 자격 증명의 안전한 저장: 비밀번호를 일반 텍스트로 저장하지 마십시오. bcrypt 또는 Argon2와 같은 강력한 비밀번호 해싱 알고리즘을 사용하십시오.
- HTTPS: 항상 HTTPS를 사용하여 클라이언트와 서버 간의 통신을 암호화하십시오.
- 입력 유효성 검사: SQL 주입 및 교차 사이트 스크립팅(XSS)과 같은 보안 취약성을 방지하기 위해 모든 사용자 입력을 유효성 검사합니다.
- 정기적인 보안 감사: 잠재적인 취약성을 식별하고 해결하기 위해 정기적인 보안 감사를 수행합니다.
- 환경 변수: 중요한 정보(API 키, 데이터베이스 자격 증명, 비밀 키)를 코드에 하드 코딩하는 대신 환경 변수로 저장합니다. 이렇게 하면 구성 관리가 쉬워지고 모범 사례 보안이 촉진됩니다.
3. 속도 제한 미들웨어
속도 제한은 서비스 거부(DoS) 공격 및 과도한 리소스 소비와 같은 남용으로부터 API를 보호합니다. 특정 시간 창 내에서 클라이언트가 만들 수 있는 요청 수를 제한합니다.
express-rate-limit
과 같은 라이브러리가 일반적으로 속도 제한에 사용됩니다. 또한 광범위한 기타 보안 개선 사항 외에 기본 속도 제한 기능을 포함하는 패키지 helmet
을 고려하십시오.
예(express-rate-limit 사용):
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15분
max: 100, // 각 IP를 windowMs당 100개 요청으로 제한
message: '이 IP에서 너무 많은 요청이 있습니다. 15분 후에 다시 시도하십시오.',
});
// 특정 경로에 속도 제한기 적용
app.use('/api/', limiter);
// 또는 모든 경로에 적용(일반적으로 모든 트래픽을 동일하게 취급해야 하는 경우가 아니면 덜 바람직함)
// app.use(limiter);
속도 제한에 대한 사용자 지정 옵션은 다음과 같습니다.
- IP 주소 기반 속도 제한: 가장 일반적인 접근 방식입니다.
- 사용자 기반 속도 제한: 사용자 인증이 필요합니다.
- 요청 메서드 기반 속도 제한: 특정 HTTP 메서드(예: POST 요청)를 제한합니다.
- 사용자 지정 스토리지: 여러 서버 인스턴스에서 더 나은 확장성을 위해 속도 제한 정보를 데이터베이스(예: Redis, MongoDB)에 저장합니다.
4. 요청 본문 파싱 미들웨어
Express.js는 기본적으로 요청 본문을 파싱하지 않습니다. JSON 및 URL 인코딩 데이터와 같은 다양한 본문 형식을 처리하려면 미들웨어를 사용해야 합니다. 이전 구현에서는 `body-parser`와 같은 패키지를 사용했을 수 있지만 현재 모범 사례는 Express v4.16부터 사용할 수 있는 Express의 내장 미들웨어를 사용하는 것입니다.
예(내장 미들웨어 사용):
app.use(express.json()); // JSON 인코딩된 요청 본문을 파싱합니다.
app.use(express.urlencoded({ extended: true })); // URL 인코딩된 요청 본문을 파싱합니다.
`express.json()` 미들웨어는 JSON 페이로드가 있는 들어오는 요청을 파싱하고 파싱된 데이터를 `req.body`에서 사용할 수 있도록 합니다. `express.urlencoded()` 미들웨어는 URL 인코딩된 페이로드가 있는 들어오는 요청을 파싱합니다. `{ extended: true }` 옵션을 사용하면 풍부한 객체와 배열을 파싱할 수 있습니다.
5. 로깅 미들웨어
효과적인 로깅은 애플리케이션을 디버깅, 모니터링 및 감사하는 데 필수적입니다. 미들웨어는 요청과 응답을 가로채서 관련 정보를 기록할 수 있습니다.
예(간단한 로깅 미들웨어):
const morgan = require('morgan'); // 널리 사용되는 HTTP 요청 로거
app.use(morgan('dev')); // 'dev' 형식으로 요청 기록
// 또 다른 예, 사용자 지정 형식
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
});
프로덕션 환경의 경우 다음과 같은 더 강력한 로깅 라이브러리(예: Winston, Bunyan)를 사용하는 것이 좋습니다.
- 로깅 수준: 심각도에 따라 로그 메시지를 분류하기 위해 다양한 로깅 수준(예:
debug
,info
,warn
,error
)을 사용합니다. - 로그 순환: 로그 파일 크기를 관리하고 디스크 공간 문제를 방지하기 위해 로그 순환을 구현합니다.
- 중앙 집중식 로깅: 더 쉬운 모니터링 및 분석을 위해 로그를 중앙 집중식 로깅 서비스(예: ELK 스택(Elasticsearch, Logstash, Kibana), Splunk)로 보냅니다.
6. 요청 유효성 검사 미들웨어
들어오는 요청을 유효성 검사하여 데이터 무결성을 보장하고 예기치 않은 동작을 방지합니다. 여기에는 요청 헤더, 쿼리 매개변수 및 요청 본문 데이터의 유효성 검사가 포함될 수 있습니다.
요청 유효성 검사를 위한 라이브러리:
- Joi: 스키마를 정의하고 데이터를 유효성 검사하기 위한 강력하고 유연한 유효성 검사 라이브러리입니다.
- Ajv: 빠른 JSON 스키마 유효성 검사기입니다.
- Express-validator: Express에서 쉽게 사용할 수 있도록 validator.js를 래핑하는 Express 미들웨어 세트입니다.
예(Joi 사용):
const Joi = require('joi');
const userSchema = Joi.object({
username: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
function validateUser(req, res, next) {
const { error } = userSchema.validate(req.body, { abortEarly: false }); // 모든 오류를 가져오려면 abortEarly를 false로 설정합니다.
if (error) {
return res.status(400).json({ errors: error.details.map(err => err.message) }); // 자세한 오류 메시지 반환
}
next();
}
app.post('/users', validateUser, (req, res) => {
// 사용자 데이터가 유효하므로 사용자 생성 진행
res.status(201).json({ message: '사용자가 성공적으로 생성되었습니다.' });
});
요청 유효성 검사를 위한 모범 사례:
- 스키마 기반 유효성 검사: 스키마를 정의하여 데이터의 예상 구조 및 데이터 형식을 지정합니다.
- 오류 처리: 유효성 검사에 실패하면 클라이언트에 유익한 오류 메시지를 반환합니다.
- 입력 삭제: 교차 사이트 스크립팅(XSS)과 같은 취약성을 방지하기 위해 사용자 입력을 삭제합니다. 입력 유효성 검사는 *무엇*이 허용되는지에 중점을 두는 반면 삭제는 유해한 요소를 제거하기 위해 입력이 *어떻게* 표시되는지에 중점을 둡니다.
- 중앙 집중식 유효성 검사: 코드 중복을 방지하기 위해 재사용 가능한 유효성 검사 미들웨어 함수를 만듭니다.
7. 응답 압축 미들웨어
클라이언트에 보내기 전에 응답을 압축하여 애플리케이션의 성능을 향상시킵니다. 이렇게 하면 전송되는 데이터 양이 줄어들어 로드 시간이 빨라집니다.
예(압축 미들웨어 사용):
const compression = require('compression');
app.use(compression()); // 응답 압축 활성화(예: gzip)
compression
미들웨어는 클라이언트의 Accept-Encoding
헤더를 기반으로 gzip 또는 deflate를 사용하여 응답을 자동으로 압축합니다. 이는 정적 자산 및 큰 JSON 응답을 제공하는 데 특히 유용합니다.
8. CORS(교차 출처 리소스 공유) 미들웨어
API 또는 웹 애플리케이션이 다른 도메인(출처)에서 요청을 수락해야 하는 경우 CORS를 구성해야 합니다. 여기에는 교차 출처 요청을 허용하기 위해 적절한 HTTP 헤더를 설정하는 것이 포함됩니다.
예(CORS 미들웨어 사용):
const cors = require('cors');
const corsOptions = {
origin: 'https://your-allowed-domain.com',
methods: 'GET,POST,PUT,DELETE',
allowedHeaders: 'Content-Type,Authorization'
};
app.use(cors(corsOptions));
// 또는 모든 출처를 허용하려면(개발 또는 내부 API의 경우 -- 주의해서 사용하십시오!)
// app.use(cors());
CORS에 대한 중요한 고려 사항:
- 출처: 무단 액세스를 방지하기 위해 허용되는 출처(도메인)를 지정합니다. 일반적으로 모든 출처(
*
)를 허용하는 것보다 특정 출처를 허용 목록에 추가하는 것이 더 안전합니다. - 메서드: 허용되는 HTTP 메서드(예: GET, POST, PUT, DELETE)를 정의합니다.
- 헤더: 허용되는 요청 헤더를 지정합니다.
- 프리플라이트 요청: 복잡한 요청(예: 사용자 지정 헤더 또는 GET, POST, HEAD 이외의 메서드 포함)의 경우 브라우저는 실제 요청이 허용되는지 확인하기 위해 프리플라이트 요청(OPTIONS)을 보냅니다. 서버는 프리플라이트 요청이 성공하려면 적절한 CORS 헤더로 응답해야 합니다.
9. 정적 파일 제공
Express.js는 정적 파일(예: HTML, CSS, JavaScript, 이미지)을 제공하기 위한 내장 미들웨어를 제공합니다. 이것은 일반적으로 애플리케이션의 프런트 엔드를 제공하는 데 사용됩니다.
예(express.static 사용):
app.use(express.static('public')); // 'public' 디렉토리에서 파일 제공
정적 자산을 public
디렉토리(또는 지정하는 다른 디렉토리)에 넣습니다. 그러면 Express.js는 파일 경로에 따라 이러한 파일을 자동으로 제공합니다.
10. 특정 작업을 위한 사용자 지정 미들웨어
논의된 패턴 외에도 애플리케이션의 특정 요구 사항에 맞는 사용자 지정 미들웨어를 만들 수 있습니다. 이를 통해 복잡한 논리를 캡슐화하고 코드 재사용성을 높일 수 있습니다.
예(기능 플래그를 위한 사용자 지정 미들웨어):
// 구성 파일에 따라 기능을 활성화/비활성화하는 사용자 지정 미들웨어
const featureFlags = require('./config/feature-flags.json');
function featureFlagMiddleware(featureName) {
return (req, res, next) => {
if (featureFlags[featureName] === true) {
next(); // 기능이 활성화되었으므로 계속 진행
} else {
res.status(404).send('기능을 사용할 수 없습니다.'); // 기능이 비활성화되었습니다.
}
};
}
// 예제 사용법
app.get('/new-feature', featureFlagMiddleware('newFeatureEnabled'), (req, res) => {
res.send('이것은 새로운 기능입니다!');
});
이 예제에서는 기능 플래그를 기반으로 특정 경로에 대한 액세스를 제어하기 위해 사용자 지정 미들웨어를 사용하는 방법을 보여줍니다. 이를 통해 개발자는 완전히 검증되지 않은 코드를 재배포하거나 변경하지 않고도 기능 릴리스를 제어할 수 있으며 이는 소프트웨어 개발에서 일반적인 관행입니다.
글로벌 애플리케이션을 위한 모범 사례 및 고려 사항
- 성능: 특히 트래픽이 많은 애플리케이션에서 미들웨어를 성능에 최적화합니다. CPU를 많이 사용하는 작업의 사용을 최소화합니다. 캐싱 전략을 사용하는 것을 고려하십시오.
- 확장성: 수평으로 확장되도록 미들웨어를 설계합니다. 세션 데이터를 메모리에 저장하지 마십시오. Redis 또는 Memcached와 같은 분산 캐시를 사용하십시오.
- 보안: 입력 유효성 검사, 인증, 권한 부여 및 일반적인 웹 취약성에 대한 보호를 포함한 보안 모범 사례를 구현합니다. 특히 국제적인 특성을 고려할 때 이는 매우 중요합니다.
- 유지 관리 용이성: 깔끔하고 잘 문서화되고 모듈식 코드를 작성합니다. 명확한 명명 규칙을 사용하고 일관된 코딩 스타일을 따릅니다. 더 쉬운 유지 관리 및 업데이트를 위해 미들웨어를 모듈화합니다.
- 테스트 가능성: 미들웨어가 올바르게 작동하고 잠재적인 버그를 조기에 발견하기 위해 단위 테스트 및 통합 테스트를 작성합니다. 다양한 환경에서 미들웨어를 테스트합니다.
- 국제화(i18n) 및 현지화(l10n): 애플리케이션이 여러 언어 또는 지역을 지원하는 경우 국제화 및 현지화를 고려합니다. 사용자 경험을 향상시키기 위해 현지화된 오류 메시지, 콘텐츠 및 형식을 제공합니다. i18next와 같은 프레임워크는 i18n 노력을 용이하게 할 수 있습니다.
- 시간대 및 날짜/시간 처리: 시간대를 염두에 두고 특히 전 세계 사용자를 대상으로 작업할 때 날짜/시간 데이터를 신중하게 처리합니다. 날짜/시간 조작을 위해 Moment.js 또는 Luxon과 같은 라이브러리를 사용하거나 가급적이면 시간대 인식을 통해 최신 Javascript 내장 Date 객체 처리를 사용합니다. 데이터베이스에 날짜/시간을 UTC 형식으로 저장하고 표시할 때 사용자 현지 시간대로 변환합니다.
- 통화 처리: 애플리케이션이 금융 거래를 처리하는 경우 통화를 올바르게 처리합니다. 적절한 통화 형식을 사용하고 여러 통화를 지원하는 것을 고려하십시오. 데이터가 일관되고 정확하게 유지 관리되는지 확인하십시오.
- 법률 및 규정 준수: 다양한 국가 또는 지역의 법률 및 규정 요구 사항(예: GDPR, CCPA)을 알고 있어야 합니다. 이러한 규정을 준수하기 위해 필요한 조치를 구현합니다.
- 접근성: 애플리케이션이 장애가 있는 사용자에게 접근 가능하도록 합니다. WCAG(웹 콘텐츠 접근성 지침)와 같은 접근성 지침을 따릅니다.
- 모니터링 및 경고: 문제를 신속하게 감지하고 대응하기 위해 포괄적인 모니터링 및 경고를 구현합니다. 서버 성능, 애플리케이션 오류 및 보안 위협을 모니터링합니다.
결론
고급 미들웨어 패턴을 마스터하는 것은 강력하고 안전하며 확장 가능한 Express.js 애플리케이션을 구축하는 데 매우 중요합니다. 이러한 패턴을 효과적으로 활용하면 기능적일 뿐만 아니라 유지 관리 가능하고 전 세계 사용자에게 적합한 애플리케이션을 만들 수 있습니다. 개발 프로세스 전반에 걸쳐 보안, 성능 및 유지 관리 용이성을 우선시하는 것을 잊지 마십시오. 신중한 계획 및 구현을 통해 Express.js 미들웨어의 힘을 활용하여 전 세계 사용자의 요구 사항을 충족하는 성공적인 웹 애플리케이션을 구축할 수 있습니다.
추가 읽을거리: