Initial commit
This commit is contained in:
451
references/top-errors.md
Normal file
451
references/top-errors.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# Common Hono Errors and Solutions
|
||||
|
||||
Complete troubleshooting guide for Hono routing and middleware errors.
|
||||
|
||||
**Last Updated**: 2025-10-22
|
||||
|
||||
---
|
||||
|
||||
## Error #1: Middleware Response Not Typed in RPC
|
||||
|
||||
**Error Message**: Client doesn't infer middleware response types
|
||||
|
||||
**Cause**: RPC mode doesn't automatically infer middleware responses by default
|
||||
|
||||
**Source**: [honojs/hono#2719](https://github.com/honojs/hono/issues/2719)
|
||||
|
||||
**Solution**: Export specific route types that include middleware
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Client doesn't see middleware response
|
||||
const route = app.get('/data', authMiddleware, handler)
|
||||
export type AppType = typeof app
|
||||
|
||||
// ✅ Correct: Export route directly
|
||||
const route = app.get('/data', authMiddleware, handler)
|
||||
export type AppType = typeof route
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #2: RPC Type Inference Slow
|
||||
|
||||
**Error Message**: IDE becomes slow or unresponsive with many routes
|
||||
|
||||
**Cause**: Complex type instantiation from `typeof app` with large number of routes
|
||||
|
||||
**Source**: [hono.dev/docs/guides/rpc](https://hono.dev/docs/guides/rpc)
|
||||
|
||||
**Solution**: Export specific route groups instead of entire app
|
||||
|
||||
```typescript
|
||||
// ❌ Slow: Export entire app
|
||||
export type AppType = typeof app
|
||||
|
||||
// ✅ Fast: Export specific routes
|
||||
const userRoutes = app.get('/users', ...).post('/users', ...)
|
||||
export type UserRoutes = typeof userRoutes
|
||||
|
||||
const postRoutes = app.get('/posts', ...).post('/posts', ...)
|
||||
export type PostRoutes = typeof postRoutes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #3: Middleware Chain Broken
|
||||
|
||||
**Error Message**: Handler not executed, middleware returns early
|
||||
|
||||
**Cause**: Forgot to call `await next()` in middleware
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Always call `await next()` unless intentionally short-circuiting
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Forgot await next()
|
||||
app.use('*', async (c, next) => {
|
||||
console.log('Before')
|
||||
// Missing: await next()
|
||||
console.log('After')
|
||||
})
|
||||
|
||||
// ✅ Correct: Call await next()
|
||||
app.use('*', async (c, next) => {
|
||||
console.log('Before')
|
||||
await next()
|
||||
console.log('After')
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #4: Validation Error Not Handled
|
||||
|
||||
**Error Message**: Validation fails silently or returns wrong status code
|
||||
|
||||
**Cause**: No custom error handler for validation failures
|
||||
|
||||
**Source**: Best practices
|
||||
|
||||
**Solution**: Use custom validation hooks
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Default 400 response with no details
|
||||
app.post('/users', zValidator('json', schema), handler)
|
||||
|
||||
// ✅ Correct: Custom error handler
|
||||
app.post(
|
||||
'/users',
|
||||
zValidator('json', schema, (result, c) => {
|
||||
if (!result.success) {
|
||||
return c.json({ error: 'Validation failed', issues: result.error.issues }, 400)
|
||||
}
|
||||
}),
|
||||
handler
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #5: Context Type Safety Lost
|
||||
|
||||
**Error Message**: `c.get()` returns `any` type
|
||||
|
||||
**Cause**: Not defining `Variables` type in Hono generic
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Define Variables type
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: No Variables type
|
||||
const app = new Hono()
|
||||
app.use('*', (c, next) => {
|
||||
c.set('user', { id: 1 }) // No type checking
|
||||
await next()
|
||||
})
|
||||
|
||||
// ✅ Correct: Define Variables type
|
||||
type Variables = {
|
||||
user: { id: number; name: string }
|
||||
}
|
||||
|
||||
const app = new Hono<{ Variables: Variables }>()
|
||||
app.use('*', (c, next) => {
|
||||
c.set('user', { id: 1, name: 'Alice' }) // Type-safe!
|
||||
await next()
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #6: Route Parameter Type Error
|
||||
|
||||
**Error Message**: `c.req.param()` returns string but number expected
|
||||
|
||||
**Cause**: Route parameters are always strings
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Use validation to transform to correct type
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Assuming number type
|
||||
app.get('/users/:id', (c) => {
|
||||
const id = c.req.param('id') // Type: string
|
||||
const user = await db.findUser(id) // Error: expects number
|
||||
return c.json({ user })
|
||||
})
|
||||
|
||||
// ✅ Correct: Validate and transform
|
||||
const idSchema = z.object({
|
||||
id: z.string().transform((val) => parseInt(val, 10)).pipe(z.number().int().positive()),
|
||||
})
|
||||
|
||||
app.get('/users/:id', zValidator('param', idSchema), async (c) => {
|
||||
const { id } = c.req.valid('param') // Type: number
|
||||
const user = await db.findUser(id)
|
||||
return c.json({ user })
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #7: Missing Error Check After Middleware
|
||||
|
||||
**Error Message**: Errors in handlers not caught
|
||||
|
||||
**Cause**: Not checking `c.error` after `await next()`
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Check `c.error` in middleware
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: No error checking
|
||||
app.use('*', async (c, next) => {
|
||||
await next()
|
||||
// Missing error check
|
||||
})
|
||||
|
||||
// ✅ Correct: Check c.error
|
||||
app.use('*', async (c, next) => {
|
||||
await next()
|
||||
|
||||
if (c.error) {
|
||||
console.error('Error:', c.error)
|
||||
// Send to error tracking service
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #8: HTTPException Misuse
|
||||
|
||||
**Error Message**: Errors not handled correctly
|
||||
|
||||
**Cause**: Throwing plain Error instead of HTTPException
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Use HTTPException for client errors
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Plain Error
|
||||
app.get('/users/:id', (c) => {
|
||||
if (!id) {
|
||||
throw new Error('ID is required') // No status code
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ Correct: HTTPException
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
app.get('/users/:id', (c) => {
|
||||
if (!id) {
|
||||
throw new HTTPException(400, { message: 'ID is required' })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #9: Query Parameter Not Validated
|
||||
|
||||
**Error Message**: Invalid query parameters cause errors
|
||||
|
||||
**Cause**: Accessing `c.req.query()` without validation
|
||||
|
||||
**Source**: Best practices
|
||||
|
||||
**Solution**: Validate query parameters
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: No validation
|
||||
app.get('/search', (c) => {
|
||||
const page = parseInt(c.req.query('page') || '1', 10) // May be NaN
|
||||
const limit = parseInt(c.req.query('limit') || '10', 10)
|
||||
// ...
|
||||
})
|
||||
|
||||
// ✅ Correct: Validate query params
|
||||
const querySchema = z.object({
|
||||
page: z.string().transform((val) => parseInt(val, 10)).pipe(z.number().int().min(1)),
|
||||
limit: z.string().transform((val) => parseInt(val, 10)).pipe(z.number().int().min(1).max(100)),
|
||||
})
|
||||
|
||||
app.get('/search', zValidator('query', querySchema), (c) => {
|
||||
const { page, limit } = c.req.valid('query') // Type-safe!
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #10: Incorrect Middleware Order
|
||||
|
||||
**Error Message**: Middleware executing in wrong order
|
||||
|
||||
**Cause**: Misunderstanding middleware execution flow
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Remember middleware runs top-to-bottom before handler, bottom-to-top after
|
||||
|
||||
```typescript
|
||||
// Middleware execution order:
|
||||
app.use('*', async (c, next) => {
|
||||
console.log('1: Before') // Runs 1st
|
||||
await next()
|
||||
console.log('4: After') // Runs 4th
|
||||
})
|
||||
|
||||
app.use('*', async (c, next) => {
|
||||
console.log('2: Before') // Runs 2nd
|
||||
await next()
|
||||
console.log('3: After') // Runs 3rd
|
||||
})
|
||||
|
||||
app.get('/', (c) => {
|
||||
console.log('Handler') // Runs in between
|
||||
return c.json({})
|
||||
})
|
||||
|
||||
// Output: 1, 2, Handler, 3, 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #11: JSON Parsing Error
|
||||
|
||||
**Error Message**: `SyntaxError: Unexpected token in JSON`
|
||||
|
||||
**Cause**: Request body is not valid JSON
|
||||
|
||||
**Source**: Common issue
|
||||
|
||||
**Solution**: Add validation and error handling
|
||||
|
||||
```typescript
|
||||
app.post('/data', async (c) => {
|
||||
try {
|
||||
const body = await c.req.json()
|
||||
return c.json({ success: true, body })
|
||||
} catch (error) {
|
||||
return c.json({ error: 'Invalid JSON' }, 400)
|
||||
}
|
||||
})
|
||||
|
||||
// Or use validator (handles automatically)
|
||||
app.post('/data', zValidator('json', schema), (c) => {
|
||||
const data = c.req.valid('json')
|
||||
return c.json({ success: true, data })
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #12: CORS Preflight Fails
|
||||
|
||||
**Error Message**: CORS preflight request fails
|
||||
|
||||
**Cause**: Missing CORS middleware or incorrect configuration
|
||||
|
||||
**Source**: Common issue
|
||||
|
||||
**Solution**: Configure CORS middleware correctly
|
||||
|
||||
```typescript
|
||||
import { cors } from 'hono/cors'
|
||||
|
||||
app.use(
|
||||
'/api/*',
|
||||
cors({
|
||||
origin: ['https://example.com'],
|
||||
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowHeaders: ['Content-Type', 'Authorization'],
|
||||
credentials: true,
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #13: Route Not Found
|
||||
|
||||
**Error Message**: 404 Not Found for existing route
|
||||
|
||||
**Cause**: Route pattern doesn't match request path
|
||||
|
||||
**Source**: Common issue
|
||||
|
||||
**Solution**: Check route pattern and parameter syntax
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Missing colon for parameter
|
||||
app.get('/users/id', handler) // Matches "/users/id" literally
|
||||
|
||||
// ✅ Correct: Parameter with colon
|
||||
app.get('/users/:id', handler) // Matches "/users/123"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #14: Response Already Sent
|
||||
|
||||
**Error Message**: Cannot set headers after response sent
|
||||
|
||||
**Cause**: Trying to modify response after calling `c.json()` or `c.text()`
|
||||
|
||||
**Source**: Common issue
|
||||
|
||||
**Solution**: Return response immediately, don't modify after
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: Trying to modify after response
|
||||
app.get('/data', (c) => {
|
||||
const response = c.json({ data: 'value' })
|
||||
c.res.headers.set('X-Custom', 'value') // Error!
|
||||
return response
|
||||
})
|
||||
|
||||
// ✅ Correct: Set headers before response
|
||||
app.get('/data', (c) => {
|
||||
c.res.headers.set('X-Custom', 'value')
|
||||
return c.json({ data: 'value' })
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error #15: Type Inference Not Working
|
||||
|
||||
**Error Message**: TypeScript not inferring types from validator
|
||||
|
||||
**Cause**: Not using `c.req.valid()` after validation
|
||||
|
||||
**Source**: Official docs
|
||||
|
||||
**Solution**: Always use `c.req.valid()` for type-safe access
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong: No type inference
|
||||
app.post('/users', zValidator('json', schema), async (c) => {
|
||||
const body = await c.req.json() // Type: any
|
||||
return c.json({ body })
|
||||
})
|
||||
|
||||
// ✅ Correct: Type-safe
|
||||
app.post('/users', zValidator('json', schema), (c) => {
|
||||
const data = c.req.valid('json') // Type: inferred from schema
|
||||
return c.json({ data })
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Middleware response not typed | RPC doesn't infer middleware | Export route, not app |
|
||||
| Slow RPC type inference | Too many routes | Export specific route groups |
|
||||
| Middleware chain broken | Missing `await next()` | Always call `await next()` |
|
||||
| Validation error unhandled | No custom hook | Use custom validation hook |
|
||||
| Context type safety lost | No Variables type | Define Variables type |
|
||||
| Route param type error | Params are strings | Use validation to transform |
|
||||
| Missing error check | Not checking `c.error` | Check `c.error` after `next()` |
|
||||
| HTTPException misuse | Using plain Error | Use HTTPException |
|
||||
| Query param not validated | Direct access | Use query validator |
|
||||
| Incorrect middleware order | Misunderstanding flow | Review execution order |
|
||||
| JSON parsing error | Invalid JSON | Add error handling |
|
||||
| CORS preflight fails | Missing CORS config | Configure CORS middleware |
|
||||
| Route not found | Wrong pattern | Check route syntax |
|
||||
| Response already sent | Modifying after send | Set headers before response |
|
||||
| Type inference not working | Not using `c.req.valid()` | Use `c.req.valid()` |
|
||||
|
||||
---
|
||||
|
||||
**Official Documentation**: https://hono.dev/docs
|
||||
Reference in New Issue
Block a user