路由参数 #

一、路径参数 #

1.1 基本路径参数 #

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

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

访问 /users/123 返回:

json
{
    "userId": "123"
}

1.2 多个路径参数 #

javascript
server.route({
    method: 'GET',
    path: '/users/{userId}/posts/{postId}',
    handler: (request, h) => {
        const { userId, postId } = request.params;
        return {
            userId,
            postId
        };
    }
});

访问 /users/1/posts/42

1.3 可选参数 #

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

javascript
server.route({
    method: 'GET',
    path: '/search/{keyword?}',
    handler: (request, h) => {
        if (request.params.keyword) {
            return { keyword: request.params.keyword };
        }
        return { message: '请输入搜索关键词' };
    }
});

1.4 多段参数 #

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

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

访问 /files/docs/api/users

json
{
    "path": "docs/api/users",
    "segments": ["docs", "api", "users"]
}

1.5 限制段数 #

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

只能匹配两段,如 /category/electronics/phones

二、参数验证 #

2.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 { id: request.params.id };
    }
});

2.2 字符串参数验证 #

javascript
server.route({
    method: 'GET',
    path: '/products/{sku}',
    options: {
        validate: {
            params: Joi.object({
                sku: Joi.string().alphanum().length(8).required()
            })
        }
    },
    handler: (request, h) => {
        return { sku: request.params.sku };
    }
});

2.3 正则验证 #

javascript
server.route({
    method: 'GET',
    path: '/orders/{orderId}',
    options: {
        validate: {
            params: Joi.object({
                orderId: Joi.string().regex(/^[A-Z]{2}\d{6}$/)
            })
        }
    },
    handler: (request, h) => {
        return { orderId: request.params.orderId };
    }
});

2.4 多参数验证 #

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

2.5 自定义验证 #

javascript
server.route({
    method: 'GET',
    path: '/items/{code}',
    options: {
        validate: {
            params: Joi.object({
                code: Joi.string().custom((value, helpers) => {
                    if (!value.startsWith('ITEM-')) {
                        return helpers.error('any.invalid');
                    }
                    return value;
                })
            })
        }
    },
    handler: (request, h) => {
        return { code: request.params.code };
    }
});

三、查询参数 #

3.1 获取查询参数 #

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

访问 /search?q=hapi&page=1&limit=10

3.2 查询参数验证 #

javascript
server.route({
    method: 'GET',
    path: '/users',
    options: {
        validate: {
            query: Joi.object({
                name: Joi.string(),
                email: Joi.string().email(),
                page: Joi.number().integer().min(1).default(1),
                limit: Joi.number().integer().min(1).max(100).default(10),
                sort: Joi.string().valid('name', 'email', 'createdAt').default('createdAt'),
                order: Joi.string().valid('asc', 'desc').default('desc')
            })
        }
    },
    handler: (request, h) => {
        return request.query;
    }
});

3.3 可选查询参数 #

javascript
server.route({
    method: 'GET',
    path: '/products',
    options: {
        validate: {
            query: Joi.object({
                category: Joi.string(),
                minPrice: Joi.number().min(0),
                maxPrice: Joi.number().min(Joi.ref('minPrice')),
                inStock: Joi.boolean()
            })
        }
    },
    handler: (request, h) => {
        const filters = request.query;
        return { filters };
    }
});

3.4 数组查询参数 #

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

访问 /articles?tags=node&tags=hapi&ids=1&ids=2

3.5 对象查询参数 #

javascript
server.route({
    method: 'GET',
    path: '/search',
    options: {
        validate: {
            query: Joi.object({
                filter: Joi.object({
                    name: Joi.string(),
                    age: Joi.number()
                })
            })
        }
    },
    handler: (request, h) => {
        return request.query.filter;
    }
});

访问 /search?filter[name]=John&filter[age]=30

四、请求体参数 #

4.1 JSON请求体 #

javascript
server.route({
    method: 'POST',
    path: '/users',
    options: {
        validate: {
            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)
            })
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

4.2 嵌套对象验证 #

javascript
server.route({
    method: 'POST',
    path: '/orders',
    options: {
        validate: {
            payload: Joi.object({
                customer: Joi.object({
                    name: Joi.string().required(),
                    email: Joi.string().email().required(),
                    address: Joi.object({
                        street: Joi.string().required(),
                        city: Joi.string().required(),
                        zipCode: Joi.string().required()
                    }).required()
                }).required(),
                items: Joi.array().items(
                    Joi.object({
                        productId: Joi.number().required(),
                        quantity: Joi.number().min(1).required()
                    })
                ).min(1).required()
            })
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

4.3 条件验证 #

javascript
server.route({
    method: 'POST',
    path: '/users',
    options: {
        validate: {
            payload: Joi.object({
                type: Joi.string().valid('individual', 'company').required(),
                name: Joi.string().when('type', {
                    is: 'individual',
                    then: Joi.required()
                }),
                companyName: Joi.string().when('type', {
                    is: 'company',
                    then: Joi.required()
                })
            })
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

五、参数转换 #

5.1 类型转换 #

javascript
server.route({
    method: 'GET',
    path: '/items/{id}',
    options: {
        validate: {
            params: Joi.object({
                id: Joi.number().integer()
            })
        }
    },
    handler: (request, h) => {
        return {
            id: request.params.id,
            type: typeof request.params.id
        };
    }
});

5.2 默认值 #

javascript
server.route({
    method: 'GET',
    path: '/list',
    options: {
        validate: {
            query: Joi.object({
                page: Joi.number().default(1),
                limit: Joi.number().default(10),
                sort: Joi.string().default('name')
            })
        }
    },
    handler: (request, h) => {
        return request.query;
    }
});

5.3 值转换 #

javascript
server.route({
    method: 'GET',
    path: '/search',
    options: {
        validate: {
            query: Joi.object({
                q: Joi.string().trim().lowercase(),
                active: Joi.boolean().truthy('yes', '1', true).falsy('no', '0', false)
            })
        }
    },
    handler: (request, h) => {
        return request.query;
    }
});

六、参数验证失败处理 #

6.1 默认处理 #

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

6.2 自定义错误消息 #

javascript
server.route({
    method: 'POST',
    path: '/users',
    options: {
        validate: {
            payload: Joi.object({
                name: Joi.string().min(2).required()
                    .messages({
                        'string.empty': '姓名不能为空',
                        'string.min': '姓名至少需要{#limit}个字符',
                        'any.required': '姓名是必填字段'
                    }),
                email: Joi.string().email().required()
                    .messages({
                        'string.email': '请输入有效的邮箱地址'
                    })
            }),
            failAction: (request, h, err) => {
                const errors = err.details.map(d => ({
                    field: d.path.join('.'),
                    message: d.message
                }));
                return h.response({ errors }).code(400).takeover();
            }
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

6.3 全局验证失败处理 #

javascript
const server = Hapi.server({
    port: 3000,
    routes: {
        validate: {
            failAction: async (request, h, err) => {
                if (process.env.NODE_ENV === 'production') {
                    console.error(err);
                    return h.response({ error: 'Invalid request' }).code(400).takeover();
                }
                throw err;
            }
        }
    }
});

七、参数组合验证 #

7.1 跨参数验证 #

javascript
server.route({
    method: 'POST',
    path: '/events',
    options: {
        validate: {
            payload: Joi.object({
                startDate: Joi.date().required(),
                endDate: Joi.date().greater(Joi.ref('startDate')).required()
            })
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

7.2 参数依赖 #

javascript
server.route({
    method: 'POST',
    path: '/shipping',
    options: {
        validate: {
            payload: Joi.object({
                method: Joi.string().valid('pickup', 'delivery').required(),
                address: Joi.string().when('method', {
                    is: 'delivery',
                    then: Joi.required()
                })
            })
        }
    },
    handler: (request, h) => {
        return request.payload;
    }
});

八、完整示例 #

8.1 用户API #

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

const userSchema = Joi.object({
    name: Joi.string().min(2).max(50).required(),
    email: Joi.string().email().required(),
    age: Joi.number().integer().min(0).max(120)
});

server.route([
    {
        method: 'GET',
        path: '/users',
        options: {
            validate: {
                query: Joi.object({
                    page: Joi.number().default(1),
                    limit: Joi.number().default(10)
                })
            }
        },
        handler: async (request, h) => {
            const { page, limit } = request.query;
            return { users: [], page, limit };
        }
    },
    {
        method: 'GET',
        path: '/users/{id}',
        options: {
            validate: {
                params: Joi.object({
                    id: Joi.number().integer().positive().required()
                })
            }
        },
        handler: async (request, h) => {
            const user = await findUser(request.params.id);
            if (!user) {
                throw Boom.notFound('User not found');
            }
            return user;
        }
    },
    {
        method: 'POST',
        path: '/users',
        options: {
            validate: {
                payload: userSchema
            }
        },
        handler: async (request, h) => {
            const user = await createUser(request.payload);
            return h.response(user).code(201);
        }
    }
]);

九、总结 #

参数处理要点:

参数类型 获取方式 验证配置
路径参数 request.params validate.params
查询参数 request.query validate.query
请求体 request.payload validate.payload
请求头 request.headers validate.headers

下一步,让我们深入学习路由处理!

最后更新:2026-03-28