应用结构 #

一、项目结构概述 #

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