Files
gh-jezweb-claude-skills-ski…/references/top-errors.md
2025-11-30 08:24:59 +08:00

11 KiB

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

Solution: Export specific route types that include middleware

// ❌ 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

Solution: Export specific route groups instead of entire app

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// 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

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

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

// ❌ 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

// ❌ 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

// ❌ 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