Skip to content

Commit 7570a71

Browse files
committed
docs: clarify error handling guidance
1 parent 69aa50c commit 7570a71

11 files changed

Lines changed: 76 additions & 3 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,20 @@ app.post(
302302

303303
Validation errors use HTTP `422` by default and can be localized through `src/locales/`.
304304

305+
## Error Handling
306+
307+
VextJS catches exceptions thrown from routes, services, and middleware through a built-in global `error-handler`.
308+
309+
- Use `app.throw(...)` when you want to return a structured HTTP error such as `404`, `409`, or a custom business code.
310+
- Throw `new VextValidationError(errors)` when you want to return a `422` response with field-level validation details.
311+
- Throw `new Error("...")` for unexpected runtime failures. VextJS will convert it to a `500 Internal Server Error`.
312+
313+
For unexpected runtime errors, detailed stack traces are intended for development and diagnostics:
314+
315+
- In development, you can expose `stack` in JSON by setting `response.hideInternalErrors = false`.
316+
- Browser requests in dev mode can also render the built-in HTML error overlay with stack frames and source context.
317+
- In production, keep `hideInternalErrors` enabled so clients receive a safe `500` response instead of internal details.
318+
305319
## Services
306320

307321
Services live in `src/services/` and are injected into `app.services` by filename:

website/docs/api/app.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ class UserService {
226226

227227
抛出 HTTP 错误,框架统一转为标准错误响应。支持三种调用形式。
228228

229+
:::info 何时使用 `app.throw()`
230+
`app.throw()` 适用于“我要主动返回一个明确的 HTTP 错误给调用方”的场景,例如 `401``404``409` 或附带业务错误码的响应。
231+
232+
如果只是发生了未预期的运行时异常,也可以直接 `throw new Error("...")`,框架同样会捕获,但这类错误会进入未知异常路径并最终转成 `500 Internal Server Error`。若需要返回字段级校验详情,则应抛出 `VextValidationError`
233+
:::
234+
229235
**函数签名**
230236

231237
```typescript

website/docs/api/config.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,14 +495,16 @@ export default {
495495

496496
### 隐藏内部错误
497497

498+
`hideInternalErrors` 只影响“未知异常”这条 500 错误路径,例如路由、service、middleware 中直接 `throw new Error("...")` 的场景。它不会改变 `app.throw(...)``VextValidationError` 这类结构化错误的状态码与响应格式。
499+
498500
`hideInternalErrors: true` 时,500 错误不暴露 stack trace:
499501

500502
```json
501503
// hideInternalErrors: true
502-
{ "code": -1, "message": "Internal Server Error" }
504+
{ "code": 500, "message": "Internal Server Error" }
503505

504506
// hideInternalErrors: false(仅开发环境使用)
505-
{ "code": -1, "message": "Cannot read properties of undefined (reading 'id')", "stack": "..." }
507+
{ "code": 500, "message": "Internal Server Error", "stack": "..." }
506508
```
507509

508510
---

website/docs/api/context.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,12 +896,14 @@ export default defineRoutes((app) => {
896896

897897
```json
898898
{
899-
"code": -1,
899+
"code": 404,
900900
"message": "用户不存在",
901901
"requestId": "550e8400-e29b-41d4-a716-446655440000"
902902
}
903903
```
904904

905+
如果你需要主动返回一个明确的 HTTP 错误,请使用 `app.throw(...)`。如果是未预期的运行时失败,也可以直接 `throw new Error("...")`,框架会把它捕获为 500;当 `response.hideInternalErrors = false` 时,开发环境下的 JSON 500 响应会额外附带 `stack`
906+
905907
### 自定义响应头 + 状态码
906908

907909
```typescript

website/docs/api/plugin-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ export default defineMiddleware(async (req, _res, next) => {
343343
});
344344
```
345345

346+
如果中间件遇到的是“我要主动返回给调用方的 HTTP 错误”,推荐使用 `req.app.throw(...)`。如果是未预期的运行时失败,也可以直接 `throw new Error("...")`,框架会将其转成 500;若需要返回字段级校验详情,则应抛出 `VextValidationError`
347+
346348
---
347349

348350
## defineMiddlewareFactory

website/docs/api/route-definition.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,8 @@ export default defineRoutes((app) => {
845845
});
846846
```
847847

848+
这里如果要主动返回 `404``401``409` 等明确的 HTTP 错误,应优先使用 `app.throw(...)`。普通 `throw new Error("...")` 也会被框架捕获,但它表示未知运行时异常,最终会进入 500 错误路径;字段级校验失败则应使用 `VextValidationError`
849+
848850
---
849851

850852
## 多路由注册

website/docs/guide/configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,8 @@ export default {
440440
};
441441
```
442442

443+
这里的 `response.hideInternalErrors` 针对的是“未知异常”的 500 路径,例如代码中直接 `throw new Error("...")`。如果你使用 `app.throw(...)` 主动抛出 `404``409` 等结构化 HTTP 错误,框架仍会按你指定的状态码和消息返回,不受该配置影响。
444+
443445
启用 `wrap: true` 后,`res.json(data)` 的实际输出:
444446

445447
```json

website/docs/guide/middleware.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,33 @@ export default defineMiddlewareFactory<CacheOptions>((options) => {
417417
- `VextValidationError`(参数校验失败)→ 422 响应 + errors 数组
418418
- 其他异常 → 500 Internal Server Error
419419

420+
### 什么时候用哪种抛错方式
421+
422+
- 需要明确的 HTTP 语义时,优先使用 `app.throw(...)`。例如 `404``401``409`,或需要附带业务错误码的场景。
423+
- 需要返回字段级校验详情时,抛出 `VextValidationError`
424+
- 发生真正的未预期异常时,可以直接 `throw new Error("...")`,框架会自动捕获并转成 `500`
425+
426+
```typescript
427+
// 结构化 HTTP 错误
428+
req.app.throw(404, "user.not_found");
429+
430+
// 字段级校验错误
431+
throw new VextValidationError([
432+
{ field: "email", message: "邮箱格式不正确" },
433+
]);
434+
435+
// 未预期的运行时错误
436+
throw new Error("Database connection lost");
437+
```
438+
439+
要注意,`throw new Error("...")` 并不表示客户端一定会看到完整错误详情。它的用途是让框架捕获“未知异常”:
440+
441+
- 默认情况下,客户端会收到安全的 `500 Internal Server Error`
442+
-`response.hideInternalErrors = false` 时,JSON 500 响应会附带 `stack`
443+
- 浏览器在 dev 模式访问出错页面时,还可能看到内置的 HTML error overlay
444+
445+
因此,若你的目标是“返回一个明确的 4xx/5xx HTTP 结果给调用方”,应使用 `app.throw(...)`,而不是依赖普通 `Error`
446+
420447
**不需要**手动编写错误处理中间件。如果需要自定义错误处理逻辑(如上报到 Sentry),推荐在插件中使用 `app.use()` 注册一个 try-catch 中间件:
421448

422449
```typescript

website/docs/guide/routing.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,14 @@ app.throw(400, "balance.insufficient", { balance: 50 }, 20001);
624624

625625
`app.throw()` 会终止当前请求处理流程(函数签名返回 `never`),无需在其后添加 `return`
626626

627+
如果这里抛出的是未预期异常,也可以直接:
628+
629+
```typescript
630+
throw new Error("Database connection lost");
631+
```
632+
633+
框架同样会捕获它,但这条路径表示“未知运行时错误”,最终会返回 `500 Internal Server Error`。开发环境下,当 `response.hideInternalErrors = false` 时,JSON 500 响应会附带 `stack`;若你的目标是主动返回一个明确的 `4xx/5xx` HTTP 结果,仍应优先使用 `app.throw(...)`
634+
627635
## 路由加载优先级
628636

629637
当存在可能冲突的路由时,`router-loader` 按以下规则处理:

website/docs/guide/services.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ declare module "vextjs" {
325325

326326
服务层中可以通过 `this.app.throw()` 抛出 HTTP 错误。框架会自动捕获并转化为统一的错误响应,无需在路由层手动 try-catch:
327327

328+
- 需要主动返回 `404``409``401` 等明确 HTTP 语义时,使用 `this.app.throw(...)`
329+
- 需要返回字段级校验详情时,抛出 `VextValidationError`
330+
- 发生未预期异常时,可以直接 `throw new Error("...")`,框架会统一转成 500
331+
328332
```typescript
329333
export default class UserService {
330334
constructor(private app: VextApp) {}
@@ -357,6 +361,8 @@ export default class UserService {
357361
}
358362
```
359363

364+
如果 service 内部直接 `throw new Error("...")`,框架也会捕获它;这条路径表示未知运行时异常,而不是主动设计好的 HTTP 错误响应。默认情况下客户端会收到安全的 `500 Internal Server Error`,开发环境下可通过 `response.hideInternalErrors = false` 额外暴露 `stack` 便于排查。
365+
360366
## 在服务中校验非 HTTP 输入
361367

362368
路由入口参数优先通过 `RouteOptions.validate` 声明,并在 handler 中使用 `req.valid()` 读取校验后的数据。对于 service 直接处理的非 HTTP 输入,例如定时任务、消息队列、外部回调或其他 service 调用,可以通过 `this.app.getValidator()` 复用当前全局校验引擎。

0 commit comments

Comments
 (0)