应用结构 #
一、项目结构概述 #
1.1 推荐目录结构 #
text
my-hapi-app/
├── src/
│ ├── index.js # 应用入口
│ ├── config/ # 配置文件
│ │ ├── index.js
│ │ └── database.js
│ ├── routes/ # 路由定义
│ │ ├── index.js
│ │ ├── users.js
│ │ └── products.js
│ ├── handlers/ # 处理函数
│ │ ├── users.js
│ │ └── products.js
│ ├── models/ # 数据模型
│ │ ├── user.js
│ │ └── product.js
│ ├── services/ # 业务逻辑
│ │ ├── userService.js
│ │ └── productService.js
│ ├── plugins/ # 自定义插件
│ │ ├── index.js
│ │ └── logger.js
│ ├── utils/ # 工具函数
│ │ ├── helpers.js
│ │ └── constants.js
│ └── validations/ # 验证规则
│ └── schemas.js
├── tests/ # 测试文件
│ ├── unit/
│ └── integration/
├── public/ # 静态文件
├── package.json
├── .env
├── .gitignore
└── README.md
1.2 简化结构(小型项目) #
text
my-hapi-app/
├── src/
│ ├── index.js
│ ├── routes.js
│ ├── handlers.js
│ └── config.js
├── package.json
└── .env
二、入口文件 #
2.1 主入口文件 #
src/index.js:
javascript
const Hapi = require('@hapi/hapi');
const config = require('./config');
const plugins = require('./plugins');
const routes = require('./routes');
const init = async () => {
const server = Hapi.server({
port: config.port,
host: config.host,
routes: {
cors: config.cors
}
});
await server.register(plugins);
server.route(routes);
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
init();
2.2 分离服务器创建 #
src/server.js:
javascript
const Hapi = require('@hapi/hapi');
const createServer = async (config) => {
const server = Hapi.server({
port: config.port,
host: config.host
});
return server;
};
module.exports = createServer;
src/index.js:
javascript
const createServer = require('./server');
const config = require('./config');
const plugins = require('./plugins');
const routes = require('./routes');
const init = async () => {
const server = await createServer(config);
await server.register(plugins);
server.route(routes);
await server.start();
console.log('Server running on %s', server.info.uri);
};
init();
三、配置管理 #
3.1 配置文件 #
src/config/index.js:
javascript
require('dotenv').config();
const config = {
port: parseInt(process.env.PORT, 10) || 3000,
host: process.env.HOST || 'localhost',
env: process.env.NODE_ENV || 'development',
cors: {
origin: ['*'],
additionalHeaders: ['cache-control', 'x-requested-with']
},
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
name: process.env.DB_NAME || 'myapp',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || ''
},
jwt: {
secret: process.env.JWT_SECRET || 'your-secret-key',
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
}
};
module.exports = config;
3.2 环境配置 #
src/config/environments.js:
javascript
const development = {
debug: true,
logging: {
level: 'debug'
}
};
const production = {
debug: false,
logging: {
level: 'info'
}
};
const test = {
debug: true,
logging: {
level: 'error'
}
};
module.exports = {
development,
production,
test
};
四、路由组织 #
4.1 路由索引 #
src/routes/index.js:
javascript
const userRoutes = require('./users');
const productRoutes = require('./products');
const authRoutes = require('./auth');
module.exports = [
...userRoutes,
...productRoutes,
...authRoutes
];
4.2 模块化路由 #
src/routes/users.js:
javascript
const handlers = require('../handlers/users');
const schemas = require('../validations/schemas');
module.exports = [
{
method: 'GET',
path: '/users',
options: {
handler: handlers.getAll,
description: '获取所有用户',
tags: ['api', 'users']
}
},
{
method: 'GET',
path: '/users/{id}',
options: {
handler: handlers.getById,
validate: schemas.userId,
description: '获取单个用户',
tags: ['api', 'users']
}
},
{
method: 'POST',
path: '/users',
options: {
handler: handlers.create,
validate: schemas.createUser,
description: '创建用户',
tags: ['api', 'users']
}
},
{
method: 'PUT',
path: '/users/{id}',
options: {
handler: handlers.update,
validate: schemas.updateUser,
description: '更新用户',
tags: ['api', 'users']
}
},
{
method: 'DELETE',
path: '/users/{id}',
options: {
handler: handlers.remove,
validate: schemas.userId,
description: '删除用户',
tags: ['api', 'users']
}
}
];
五、处理函数 #
5.1 处理函数模块 #
src/handlers/users.js:
javascript
const userService = require('../services/userService');
const Boom = require('@hapi/boom');
const getAll = async (request, h) => {
const { page = 1, limit = 10 } = request.query;
const users = await userService.findAll({ page, limit });
return users;
};
const getById = async (request, h) => {
const { id } = request.params;
const user = await userService.findById(id);
if (!user) {
throw Boom.notFound('User not found');
}
return user;
};
const create = async (request, h) => {
const userData = request.payload;
const user = await userService.create(userData);
return h.response(user).code(201);
};
const update = async (request, h) => {
const { id } = request.params;
const userData = request.payload;
const user = await userService.update(id, userData);
if (!user) {
throw Boom.notFound('User not found');
}
return user;
};
const remove = async (request, h) => {
const { id } = request.params;
await userService.remove(id);
return h.response().code(204);
};
module.exports = {
getAll,
getById,
create,
update,
remove
};
六、服务层 #
6.1 服务模块 #
src/services/userService.js:
javascript
const User = require('../models/user');
const findAll = async ({ page, limit }) => {
const offset = (page - 1) * limit;
const users = await User.findAll({ offset, limit });
return users;
};
const findById = async (id) => {
return await User.findById(id);
};
const create = async (userData) => {
return await User.create(userData);
};
const update = async (id, userData) => {
return await User.update(id, userData);
};
const remove = async (id) => {
return await User.remove(id);
};
module.exports = {
findAll,
findById,
create,
update,
remove
};
七、验证规则 #
7.1 验证模块 #
src/validations/schemas.js:
javascript
const Joi = require('joi');
const userId = {
params: Joi.object({
id: Joi.number().integer().positive().required()
})
};
const createUser = {
payload: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
age: Joi.number().integer().min(0).max(120)
})
};
const updateUser = {
params: Joi.object({
id: Joi.number().integer().positive().required()
}),
payload: Joi.object({
name: Joi.string().min(2).max(50),
email: Joi.string().email(),
age: Joi.number().integer().min(0).max(120)
}).min(1)
};
const pagination = {
query: Joi.object({
page: Joi.number().integer().positive().default(1),
limit: Joi.number().integer().positive().max(100).default(10)
})
};
module.exports = {
userId,
createUser,
updateUser,
pagination
};
八、插件组织 #
8.1 插件索引 #
src/plugins/index.js:
javascript
const Inert = require('@hapi/inert');
const Vision = require('@hapi/vision');
const logger = require('./logger');
const errorHandler = require('./errorHandler');
module.exports = [
Inert,
Vision,
logger,
errorHandler
];
8.2 自定义插件 #
src/plugins/logger.js:
javascript
const logger = {
name: 'logger',
version: '1.0.0',
register: async (server, options) => {
server.events.on('response', (request) => {
console.log({
method: request.method,
path: request.path,
status: request.response.statusCode,
time: request.info.responded - request.info.received
});
});
}
};
module.exports = logger;
src/plugins/errorHandler.js:
javascript
const errorHandler = {
name: 'errorHandler',
version: '1.0.0',
register: async (server, options) => {
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (response.isBoom) {
const error = response;
return h.response({
error: {
statusCode: error.output.statusCode,
message: error.message
}
}).code(error.output.statusCode);
}
return h.continue;
});
}
};
module.exports = errorHandler;
九、工具函数 #
9.1 工具模块 #
src/utils/helpers.js:
javascript
const formatDate = (date) => {
return new Date(date).toISOString();
};
const generateId = () => {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
};
const paginate = (page, limit) => {
const offset = (page - 1) * limit;
return { offset, limit };
};
const pick = (obj, keys) => {
return keys.reduce((result, key) => {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
return result;
}, {});
};
module.exports = {
formatDate,
generateId,
paginate,
pick
};
src/utils/constants.js:
javascript
const HTTP_STATUS = {
OK: 200,
CREATED: 201,
NO_CONTENT: 204,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_ERROR: 500
};
const USER_ROLES = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest'
};
module.exports = {
HTTP_STATUS,
USER_ROLES
};
十、完整示例 #
10.1 项目结构 #
text
my-hapi-app/
├── src/
│ ├── index.js
│ ├── config/
│ │ └── index.js
│ ├── routes/
│ │ ├── index.js
│ │ └── users.js
│ ├── handlers/
│ │ └── users.js
│ ├── services/
│ │ └── userService.js
│ ├── validations/
│ │ └── schemas.js
│ ├── plugins/
│ │ ├── index.js
│ │ └── errorHandler.js
│ └── utils/
│ └── helpers.js
├── package.json
└── .env
10.2 运行项目 #
bash
npm run dev
十一、总结 #
应用结构要点:
| 目录 | 说明 |
|---|---|
| config/ | 配置文件 |
| routes/ | 路由定义 |
| handlers/ | 处理函数 |
| services/ | 业务逻辑 |
| models/ | 数据模型 |
| plugins/ | 自定义插件 |
| validations/ | 验证规则 |
| utils/ | 工具函数 |
下一步,让我们深入学习Hapi的路由系统!
最后更新:2026-03-28