第一个应用 #

一、创建服务器 #

1.1 最简服务器 #

创建一个最简单的Hapi服务器:

javascript
const Hapi = require('@hapi/hapi');

const init = async () => {
    const server = Hapi.server({
        port: 3000,
        host: 'localhost'
    });

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

init();

1.2 服务器配置选项 #

javascript
const server = Hapi.server({
    port: 3000,
    host: 'localhost',
    app: {
        customConfig: 'value'
    },
    routes: {
        cors: true,
        validate: {
            failAction: (request, h, err) => {
                throw err;
            }
        }
    }
});

1.3 服务器信息 #

javascript
await server.start();

console.log('服务器URI:', server.info.uri);
console.log('服务器地址:', server.info.address);
console.log('服务器端口:', server.info.port);
console.log('服务器协议:', server.info.protocol);

二、Hello World #

2.1 基本路由 #

javascript
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 World!';
        }
    });

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

init();

2.2 返回JSON #

javascript
server.route({
    method: 'GET',
    path: '/api',
    handler: (request, h) => {
        return {
            message: 'Hello World',
            timestamp: new Date().toISOString()
        };
    }
});

2.3 带状态码的响应 #

javascript
server.route({
    method: 'GET',
    path: '/created',
    handler: (request, h) => {
        return h.response({
            message: 'Resource created'
        }).code(201);
    }
});

三、路由基础 #

3.1 定义路由 #

Hapi使用配置对象定义路由:

javascript
server.route({
    method: 'GET',
    path: '/hello',
    handler: (request, h) => {
        return 'Hello!';
    }
});

3.2 多个路由 #

使用数组定义多个路由:

javascript
server.route([
    {
        method: 'GET',
        path: '/',
        handler: (request, h) => {
            return 'Home Page';
        }
    },
    {
        method: 'GET',
        path: '/about',
        handler: (request, h) => {
            return 'About Page';
        }
    },
    {
        method: 'GET',
        path: '/contact',
        handler: (request, h) => {
            return 'Contact Page';
        }
    }
]);

3.3 路径参数 #

javascript
server.route({
    method: 'GET',
    path: '/users/{id}',
    handler: (request, h) => {
        return `User ID: ${request.params.id}`;
    }
});

3.4 可选参数 #

javascript
server.route({
    method: 'GET',
    path: '/users/{id?}',
    handler: (request, h) => {
        if (request.params.id) {
            return `User ID: ${request.params.id}`;
        }
        return 'All Users';
    }
});

3.5 多段参数 #

javascript
server.route({
    method: 'GET',
    path: '/files/{path*}',
    handler: (request, h) => {
        return `File path: ${request.params.path}`;
    }
});

访问 /files/docs/api/users 返回 File path: docs/api/users

四、处理函数 #

4.1 基本处理函数 #

javascript
const handler = (request, h) => {
    return 'Response';
};

server.route({
    method: 'GET',
    path: '/simple',
    handler: handler
});

4.2 异步处理函数 #

javascript
server.route({
    method: 'GET',
    path: '/async',
    handler: async (request, h) => {
        const data = await fetchData();
        return data;
    }
});

4.3 返回不同类型 #

javascript
server.route({
    method: 'GET',
    path: '/json',
    handler: (request, h) => {
        return { type: 'json' };
    }
});

server.route({
    method: 'GET',
    path: '/text',
    handler: (request, h) => {
        return h.response('Plain text').type('text/plain');
    }
});

server.route({
    method: 'GET',
    path: '/html',
    handler: (request, h) => {
        return h.response('<h1>HTML</h1>').type('text/html');
    }
});

五、响应对象 #

5.1 h.response() #

javascript
server.route({
    method: 'GET',
    path: '/custom',
    handler: (request, h) => {
        return h.response({ message: 'Custom response' })
            .code(200)
            .header('X-Custom-Header', 'value')
            .type('application/json');
    }
});

5.2 设置响应头 #

javascript
server.route({
    method: 'GET',
    path: '/headers',
    handler: (request, h) => {
        return h.response('Response with headers')
            .header('X-API-Version', '1.0')
            .header('Cache-Control', 'no-cache');
    }
});

5.3 设置状态码 #

javascript
server.route({
    method: 'POST',
    path: '/create',
    handler: (request, h) => {
        return h.response({ id: 1 })
            .code(201)
            .message('Resource created');
    }
});

5.4 重定向 #

javascript
server.route({
    method: 'GET',
    path: '/redirect',
    handler: (request, h) => {
        return h.redirect('/target');
    }
});

server.route({
    method: 'GET',
    path: '/redirect-permanent',
    handler: (request, h) => {
        return h.redirect('/target').permanent(true);
    }
});

六、请求对象 #

6.1 获取请求信息 #

javascript
server.route({
    method: 'GET',
    path: '/info',
    handler: (request, h) => {
        return {
            method: request.method,
            path: request.path,
            headers: request.headers,
            query: request.query,
            params: request.params,
            info: {
                remoteAddress: request.info.remoteAddress,
                host: request.info.host
            }
        };
    }
});

6.2 获取查询参数 #

javascript
server.route({
    method: 'GET',
    path: '/search',
    handler: (request, h) => {
        const { q, page, limit } = request.query;
        return {
            query: q,
            page: page || 1,
            limit: limit || 10
        };
    }
});

访问 /search?q=hapi&page=2&limit=20

6.3 获取请求体 #

javascript
server.route({
    method: 'POST',
    path: '/users',
    handler: (request, h) => {
        const userData = request.payload;
        return {
            message: 'User created',
            data: userData
        };
    }
});

七、错误处理 #

7.1 使用Boom #

javascript
const Boom = require('@hapi/boom');

server.route({
    method: 'GET',
    path: '/users/{id}',
    handler: (request, h) => {
        const user = findUser(request.params.id);
        
        if (!user) {
            throw Boom.notFound('User not found');
        }
        
        return user;
    }
});

7.2 常用Boom错误 #

javascript
const Boom = require('@hapi/boom');

server.route({
    method: 'GET',
    path: '/errors/{type}',
    handler: (request, h) => {
        const { type } = request.params;
        
        switch (type) {
            case 'badRequest':
                throw Boom.badRequest('Invalid request');
            case 'unauthorized':
                throw Boom.unauthorized('Authentication required');
            case 'forbidden':
                throw Boom.forbidden('Access denied');
            case 'notFound':
                throw Boom.notFound('Resource not found');
            case 'conflict':
                throw Boom.conflict('Resource already exists');
            case 'internal':
                throw Boom.internal('Internal server error');
            default:
                return { message: 'No error' };
        }
    }
});

7.3 全局错误处理 #

javascript
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,
                details: error.data
            }
        }).code(error.output.statusCode);
    }
    
    return h.continue;
});

八、完整示例 #

8.1 用户API #

javascript
const Hapi = require('@hapi/hapi');
const Boom = require('@hapi/boom');

const users = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob', email: 'bob@example.com' }
];

const init = async () => {
    const server = Hapi.server({
        port: 3000,
        host: 'localhost'
    });

    server.route([
        {
            method: 'GET',
            path: '/users',
            handler: (request, h) => {
                return users;
            }
        },
        {
            method: 'GET',
            path: '/users/{id}',
            handler: (request, h) => {
                const user = users.find(u => u.id === parseInt(request.params.id));
                if (!user) {
                    throw Boom.notFound('User not found');
                }
                return user;
            }
        },
        {
            method: 'POST',
            path: '/users',
            handler: (request, h) => {
                const user = {
                    id: users.length + 1,
                    ...request.payload
                };
                users.push(user);
                return h.response(user).code(201);
            }
        },
        {
            method: 'PUT',
            path: '/users/{id}',
            handler: (request, h) => {
                const index = users.findIndex(u => u.id === parseInt(request.params.id));
                if (index === -1) {
                    throw Boom.notFound('User not found');
                }
                users[index] = { ...users[index], ...request.payload };
                return users[index];
            }
        },
        {
            method: 'DELETE',
            path: '/users/{id}',
            handler: (request, h) => {
                const index = users.findIndex(u => u.id === parseInt(request.params.id));
                if (index === -1) {
                    throw Boom.notFound('User not found');
                }
                users.splice(index, 1);
                return h.response().code(204);
            }
        }
    ]);

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

init();

九、运行和测试 #

9.1 启动服务器 #

bash
node server.js

9.2 测试API #

bash
curl http://localhost:3000/users
curl http://localhost:3000/users/1
curl -X POST -H "Content-Type: application/json" -d '{"name":"Charlie","email":"charlie@example.com"}' http://localhost:3000/users

十、总结 #

第一个应用要点:

概念 说明
服务器 Hapi.server()创建
路由 server.route()定义
处理函数 handler处理请求
响应 h.response()创建响应
错误 Boom处理错误

下一步,让我们学习Hapi应用的最佳结构!

最后更新:2026-03-28