728x90
반응형
1. Hapi.js란
Hapi.js는 Walmart에서 개발한 엔터프라이즈급 웹 프레임워크입니다. 설정 기반(configuration-centric) 접근 방식을 사용하며, 입력 검증, 인증, 캐싱 등의 기능이 내장되어 있습니다.
npm install @hapi/hapi
const Hapi = require('@hapi/hapi');
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello Hapi!';
}
});
await server.start();
console.log('서버 실행 중:', server.info.uri);
};
init();
2. 서버 설정
2.1 서버 옵션
const Hapi = require('@hapi/hapi');
const server = Hapi.server({
port: 3000,
host: 'localhost',
routes: {
cors: true, // CORS 허용
validate: {
failAction: async (request, h, err) => {
throw err; // 검증 실패 시 에러 반환
}
}
},
debug: {
request: ['error'] // 에러 로깅
}
});
2.2 서버 이벤트
// 요청 로깅
server.events.on('response', (request) => {
console.log(
`${request.method.toUpperCase()} ${request.path} ` +
`${request.response.statusCode}`
);
});
// 시작/종료 이벤트
server.events.on('start', () => {
console.log('서버 시작됨');
});
server.events.on('stop', () => {
console.log('서버 종료됨');
});
3. 라우팅
3.1 기본 라우팅
// GET 요청
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Home';
}
});
// POST 요청
server.route({
method: 'POST',
path: '/users',
handler: (request, h) => {
const user = request.payload; // 요청 본문
return h.response({ created: user }).code(201);
}
});
// 여러 메서드
server.route({
method: ['GET', 'POST'],
path: '/multi',
handler: (request, h) => {
return `Method: ${request.method}`;
}
});
3.2 경로 파라미터
// 필수 파라미터
server.route({
method: 'GET',
path: '/users/{id}',
handler: (request, h) => {
const { id } = request.params;
return { userId: id };
}
});
// 선택적 파라미터
server.route({
method: 'GET',
path: '/files/{path*}', // 와일드카드
handler: (request, h) => {
return { path: request.params.path };
}
});
// 여러 파라미터
server.route({
method: 'GET',
path: '/users/{userId}/posts/{postId}',
handler: (request, h) => {
const { userId, postId } = request.params;
return { userId, postId };
}
});
3.3 쿼리 파라미터
server.route({
method: 'GET',
path: '/search',
handler: (request, h) => {
const { q, page, limit } = request.query;
return { query: q, page, limit };
}
});
반응형
4. 요청 검증
Hapi는 Joi를 사용한 강력한 입력 검증을 제공합니다.
npm install joi
const Joi = require('joi');
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150)
})
}
},
handler: (request, h) => {
return h.response(request.payload).code(201);
}
});
// 파라미터 검증
server.route({
method: 'GET',
path: '/users/{id}',
options: {
validate: {
params: Joi.object({
id: Joi.number().integer().positive().required()
})
}
},
handler: (request, h) => {
return { id: request.params.id };
}
});
// 쿼리 검증
server.route({
method: 'GET',
path: '/search',
options: {
validate: {
query: Joi.object({
q: Joi.string().required(),
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(10)
})
}
},
handler: (request, h) => {
return request.query;
}
});
5. 응답 처리
5.1 응답 툴킷 (h)
server.route({
method: 'GET',
path: '/response-examples',
handler: (request, h) => {
// 기본 응답
return 'Hello';
// 상태 코드 설정
return h.response({ message: 'Created' }).code(201);
// 헤더 설정
return h.response({ data: [] })
.header('X-Custom', 'value')
.type('application/json');
// 리다이렉트
return h.redirect('/other');
// 파일 응답
return h.file('path/to/file.pdf');
}
});
5.2 뷰 렌더링
npm install @hapi/vision ejs
const Vision = require('@hapi/vision');
await server.register(Vision);
server.views({
engines: { ejs: require('ejs') },
path: 'views'
});
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return h.view('index', {
title: 'Hapi 앱',
users: [{ name: '홍길동' }]
});
}
});
6. 플러그인
Hapi의 핵심 기능인 플러그인 시스템입니다.
6.1 플러그인 등록
// 내장 플러그인 (정적 파일)
const Inert = require('@hapi/inert');
await server.register(Inert);
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: 'public'
}
}
});
6.2 커스텀 플러그인
// plugins/logger.js
const loggerPlugin = {
name: 'logger',
version: '1.0.0',
register: async (server, options) => {
server.ext('onRequest', (request, h) => {
console.log(`${request.method.toUpperCase()} ${request.path}`);
return h.continue;
});
}
};
// 사용
await server.register({
plugin: loggerPlugin,
options: {}
});
6.3 라우트 플러그인
// plugins/users.js
const usersPlugin = {
name: 'users',
version: '1.0.0',
register: async (server, options) => {
server.route([
{
method: 'GET',
path: '/api/users',
handler: (request, h) => {
return [{ id: 1, name: '홍길동' }];
}
},
{
method: 'GET',
path: '/api/users/{id}',
handler: (request, h) => {
return { id: request.params.id, name: '홍길동' };
}
}
]);
}
};
await server.register(usersPlugin);
7. 인증
7.1 기본 인증
npm install @hapi/basic
const Basic = require('@hapi/basic');
await server.register(Basic);
const validate = async (request, username, password) => {
// 사용자 검증 로직
const isValid = username === 'admin' && password === 'secret';
const credentials = { name: username };
return { isValid, credentials };
};
server.auth.strategy('simple', 'basic', { validate });
server.auth.default('simple');
server.route({
method: 'GET',
path: '/private',
handler: (request, h) => {
return `Hello, ${request.auth.credentials.name}`;
}
});
// 인증 없이 접근 가능한 라우트
server.route({
method: 'GET',
path: '/public',
options: {
auth: false
},
handler: (request, h) => {
return 'Public content';
}
});
7.2 JWT 인증
npm install hapi-auth-jwt2 jsonwebtoken
const jwt = require('jsonwebtoken');
const HapiJwt = require('hapi-auth-jwt2');
const secret = 'your-secret-key';
await server.register(HapiJwt);
server.auth.strategy('jwt', 'jwt', {
key: secret,
validate: async (decoded, request, h) => {
// 토큰 검증 로직
return { isValid: true, credentials: decoded };
},
verifyOptions: { algorithms: ['HS256'] }
});
server.auth.default('jwt');
// 로그인 (토큰 발급)
server.route({
method: 'POST',
path: '/login',
options: { auth: false },
handler: (request, h) => {
const { username, password } = request.payload;
// 사용자 검증
if (username === 'admin' && password === 'password') {
const token = jwt.sign({ username }, secret, { expiresIn: '1h' });
return { token };
}
return h.response({ error: 'Invalid credentials' }).code(401);
}
});
8. 캐싱
server.route({
method: 'GET',
path: '/cached',
options: {
cache: {
expiresIn: 30 * 1000, // 30초
privacy: 'private'
}
},
handler: (request, h) => {
return { data: 'cached content', time: Date.now() };
}
});
// 서버 측 캐싱
const cache = server.cache({
segment: 'users',
expiresIn: 60 * 1000
});
server.route({
method: 'GET',
path: '/users/{id}',
handler: async (request, h) => {
const { id } = request.params;
let user = await cache.get(id);
if (!user) {
user = await fetchUserFromDB(id);
await cache.set(id, user);
}
return user;
}
});
9. 실전 API 서버
const Hapi = require('@hapi/hapi');
const Joi = require('joi');
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost',
routes: {
cors: {
origin: ['*']
}
}
});
// 데이터 저장소
const users = new Map();
let nextId = 1;
// 로깅
server.events.on('response', (request) => {
console.log(
`${request.method.toUpperCase()} ${request.path} ` +
`${request.response.statusCode}`
);
});
// 사용자 목록
server.route({
method: 'GET',
path: '/api/users',
handler: (request, h) => {
return Array.from(users.values());
}
});
// 사용자 조회
server.route({
method: 'GET',
path: '/api/users/{id}',
options: {
validate: {
params: Joi.object({
id: Joi.number().integer().positive().required()
})
}
},
handler: (request, h) => {
const user = users.get(request.params.id);
if (!user) {
return h.response({ error: 'User not found' }).code(404);
}
return user;
}
});
// 사용자 생성
server.route({
method: 'POST',
path: '/api/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required()
})
}
},
handler: (request, h) => {
const user = {
id: nextId++,
...request.payload,
createdAt: new Date()
};
users.set(user.id, user);
return h.response(user).code(201);
}
});
// 사용자 수정
server.route({
method: 'PUT',
path: '/api/users/{id}',
options: {
validate: {
params: Joi.object({
id: Joi.number().integer().positive().required()
}),
payload: Joi.object({
name: Joi.string().min(2).max(50),
email: Joi.string().email()
})
}
},
handler: (request, h) => {
const { id } = request.params;
const user = users.get(id);
if (!user) {
return h.response({ error: 'User not found' }).code(404);
}
const updated = { ...user, ...request.payload };
users.set(id, updated);
return updated;
}
});
// 사용자 삭제
server.route({
method: 'DELETE',
path: '/api/users/{id}',
options: {
validate: {
params: Joi.object({
id: Joi.number().integer().positive().required()
})
}
},
handler: (request, h) => {
const { id } = request.params;
if (!users.delete(id)) {
return h.response({ error: 'User not found' }).code(404);
}
return h.response().code(204);
}
});
await server.start();
console.log('Hapi API 서버 실행 중:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
init();
결론
Hapi.js는 설정 기반의 엔터프라이즈급 웹 프레임워크입니다. Joi를 통한 강력한 입력 검증, 플러그인 시스템, 내장 인증과 캐싱 기능이 특징입니다. 미들웨어 대신 라이프사이클 훅(ext)을 사용하고, 응답 툴킷(h)으로 다양한 응답을 처리합니다. 보안과 안정성이 중요한 엔터프라이즈 환경에 적합합니다.
728x90
반응형
'Node.js' 카테고리의 다른 글
| Node.js의 NestJS 프레임워크 (0) | 2026.03.10 |
|---|---|
| Node.js의 Express.js 프레임워크 (0) | 2026.03.09 |
| Node.js의 HTTP 클라이언트 만들기 (0) | 2026.03.08 |
| Node.js의 Koa.js 프레임워크 (0) | 2026.03.08 |
| Node.js의 HTTP 서버 만들기 (0) | 2026.03.08 |