路由基础 #

一、路由概念 #

1.1 什么是路由? #

路由是指确定应用程序如何响应客户端对特定端点的请求。Hapi使用配置对象的方式定义路由,代码更清晰、可维护。

1.2 路由结构 #

javascript
server.route({
    method: 'GET',
    path: '/hello',
    handler: (request, h) => {
        return 'Hello!';
    }
});
组成部分 说明
method HTTP请求方法
path URL路径
handler 处理函数
options 配置选项(可选)

1.3 基本示例 #

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

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

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

    await server.start();
};

init();

二、路由定义方式 #

2.1 单个路由 #

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

2.2 多个路由 #

使用数组定义多个路由:

javascript
server.route([
    {
        method: 'GET',
        path: '/users',
        handler: (request, h) => {
            return { users: [] };
        }
    },
    {
        method: 'POST',
        path: '/users',
        handler: (request, h) => {
            return { message: 'User created' };
        }
    },
    {
        method: 'GET',
        path: '/users/{id}',
        handler: (request, h) => {
            return { id: request.params.id };
        }
    }
]);

2.3 路由配置选项 #

javascript
server.route({
    method: 'POST',
    path: '/users',
    options: {
        description: '创建新用户',
        notes: '需要管理员权限',
        tags: ['api', 'users'],
        auth: 'jwt',
        validate: {
            payload: Joi.object({
                name: Joi.string().required(),
                email: Joi.string().email().required()
            })
        },
        response: {
            schema: Joi.object({
                id: Joi.number(),
                name: Joi.string(),
                email: Joi.string()
            })
        }
    },
    handler: createUser
});

三、路径匹配 #

3.1 静态路径 #

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

3.2 路径参数 #

使用 {param} 定义路径参数:

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

3.3 可选参数 #

使用 {param?} 定义可选参数:

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.4 多段参数 #

使用 {param*} 匹配多段路径:

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

3.5 参数数量限制 #

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

只能匹配两段路径,如 /files/docs/api

四、路径参数验证 #

4.1 参数验证 #

javascript
const Joi = require('joi');

server.route({
    method: 'GET',
    path: '/users/{id}',
    options: {
        validate: {
            params: Joi.object({
                id: Joi.number().integer().positive().required()
            })
        }
    },
    handler: (request, h) => {
        return { userId: request.params.id };
    }
});

4.2 正则验证 #

javascript
server.route({
    method: 'GET',
    path: '/users/{id}',
    options: {
        validate: {
            params: Joi.object({
                id: Joi.string().regex(/^[a-zA-Z0-9]+$/)
            })
        }
    },
    handler: (request, h) => {
        return { userId: request.params.id };
    }
});

五、查询参数 #

5.1 获取查询参数 #

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

5.2 查询参数验证 #

javascript
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.3 数组查询参数 #

javascript
server.route({
    method: 'GET',
    path: '/filter',
    options: {
        validate: {
            query: Joi.object({
                tags: Joi.array().items(Joi.string()).single()
            })
        }
    },
    handler: (request, h) => {
        return { tags: request.query.tags };
    }
});

访问 /filter?tags=node&tags=hapi

六、路由选项 #

6.1 常用选项 #

javascript
server.route({
    method: 'POST',
    path: '/users',
    options: {
        description: '创建用户',
        notes: '创建一个新的用户账户',
        tags: ['api'],
        auth: false,
        cors: true,
        timeout: {
            server: 30000
        },
        payload: {
            maxBytes: 1048576,
            parse: true
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

6.2 认证配置 #

javascript
server.route({
    method: 'GET',
    path: '/profile',
    options: {
        auth: 'jwt',
        description: '获取用户资料'
    },
    handler: (request, h) => {
        return { user: request.auth.credentials };
    }
});

6.3 CORS配置 #

javascript
server.route({
    method: 'GET',
    path: '/api/data',
    options: {
        cors: {
            origin: ['https://example.com'],
            headers: ['Accept', 'Content-Type'],
            additionalHeaders: ['X-Custom-Header']
        }
    },
    handler: (request, h) => {
        return { data: 'value' };
    }
});

6.4 文件上传配置 #

javascript
server.route({
    method: 'POST',
    path: '/upload',
    options: {
        payload: {
            output: 'file',
            parse: true,
            maxBytes: 10 * 1024 * 1024,
            multipart: true
        }
    },
    handler: (request, h) => {
        const file = request.payload.file;
        return { filename: file.filename };
    }
});

七、路由前缀 #

7.1 服务器级别前缀 #

javascript
const server = Hapi.server({
    port: 3000,
    routes: {
        prefix: '/api'
    }
});

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

实际路径为 /api/users

7.2 插件级别前缀 #

javascript
await server.register({
    plugin: myPlugin,
    options: {},
    routes: {
        prefix: '/v1'
    }
});

八、路由匹配顺序 #

8.1 匹配规则 #

Hapi按照定义顺序匹配路由,更具体的路由应该放在前面:

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

8.2 路由冲突 #

避免路由冲突:

javascript
server.route([
    {
        method: 'GET',
        path: '/users/{id}',
        handler: getUserById
    },
    {
        method: 'GET',
        path: '/users/profile',
        handler: getProfile
    }
]);

访问 /users/profile 会匹配第一个路由,profile 被当作 id

正确顺序:

javascript
server.route([
    {
        method: 'GET',
        path: '/users/profile',
        handler: getProfile
    },
    {
        method: 'GET',
        path: '/users/{id}',
        handler: getUserById
    }
]);

九、路由默认值 #

9.1 默认处理 #

javascript
server.route({
    method: 'GET',
    path: '/{any*}',
    handler: (request, h) => {
        return h.response({ error: 'Not Found' }).code(404);
    }
});

9.2 默认验证失败处理 #

javascript
const server = Hapi.server({
    port: 3000,
    routes: {
        validate: {
            failAction: (request, h, err) => {
                console.error(err);
                throw err;
            }
        }
    }
});

十、路由分组 #

10.1 按功能分组 #

javascript
const userRoutes = [
    {
        method: 'GET',
        path: '/users',
        handler: getUsers
    },
    {
        method: 'GET',
        path: '/users/{id}',
        handler: getUser
    },
    {
        method: 'POST',
        path: '/users',
        handler: createUser
    }
];

const productRoutes = [
    {
        method: 'GET',
        path: '/products',
        handler: getProducts
    },
    {
        method: 'POST',
        path: '/products',
        handler: createProduct
    }
];

server.route([...userRoutes, ...productRoutes]);

10.2 模块化路由 #

routes/users.js:

javascript
const handlers = require('../handlers/users');

module.exports = [
    {
        method: 'GET',
        path: '/users',
        handler: handlers.getAll
    },
    {
        method: 'POST',
        path: '/users',
        handler: handlers.create
    }
];

routes/index.js:

javascript
const userRoutes = require('./users');
const productRoutes = require('./products');

module.exports = [
    ...userRoutes,
    ...productRoutes
];

十一、总结 #

路由基础要点:

概念 说明
路由结构 method, path, handler, options
路径参数 {param} 定义参数
可选参数 {param?} 可选
多段参数 {param*} 多段匹配
查询参数 request.query 获取
验证 validate 选项配置

下一步,让我们深入学习路由方法!

最后更新:2026-03-28