Skip to main content

Overview

Hono provides robust error handling mechanisms to help you manage both expected and unexpected errors in your applications. Proper error handling ensures your API returns meaningful responses and maintains stability.

HTTPException

The HTTPException class is the primary way to throw HTTP errors in Hono. It allows you to return specific HTTP status codes with custom messages.

Basic Usage

import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'

const app = new Hono()

app.post('/auth', async (c) => {
  const { username, password } = await c.req.json()
  
  const authorized = await verifyCredentials(username, password)
  
  if (!authorized) {
    throw new HTTPException(401, { 
      message: 'Invalid credentials' 
    })
  }
  
  return c.json({ message: 'Authenticated' })
})

HTTPException Options

The HTTPException constructor accepts:
  • status - HTTP status code (default: 500)
  • options.message - Error message
  • options.res - Custom Response object
  • options.cause - Original error cause
app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  
  try {
    const user = await db.users.findById(id)
    if (!user) {
      throw new HTTPException(404, { 
        message: `User ${id} not found` 
      })
    }
    return c.json(user)
  } catch (error) {
    throw new HTTPException(500, { 
      message: 'Database error',
      cause: error 
    })
  }
})

Custom Response

Return a custom Response object with the exception:
app.post('/api/data', async (c) => {
  const data = await c.req.json()
  
  if (!isValid(data)) {
    const res = new Response(
      JSON.stringify({ 
        error: 'Validation failed', 
        details: getValidationErrors(data) 
      }), 
      {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      }
    )
    throw new HTTPException(400, { res })
  }
  
  return c.json({ success: true })
})

Global Error Handler

Define a custom error handler to catch all errors in your application:
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'

const app = new Hono()

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    // Get the custom response
    return err.getResponse()
  }
  
  // Handle other errors
  console.error(`${err}`)
  
  return c.text('Internal Server Error', 500)
})

app.get('/', (c) => {
  throw new Error('Something went wrong!')
})

Structured Error Responses

Return consistent error response structures:
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json(
      {
        error: {
          message: err.message,
          status: err.status,
        },
      },
      err.status
    )
  }
  
  return c.json(
    {
      error: {
        message: 'Internal Server Error',
        status: 500,
      },
    },
    500
  )
})

NotFound Handler

Handle 404 errors with a custom not found handler:
const app = new Hono()

app.notFound((c) => {
  return c.text('Custom 404 Message', 404)
})

// Or return JSON
app.notFound((c) => {
  return c.json(
    {
      error: 'Not Found',
      path: c.req.path,
    },
    404
  )
})

Error Handling Middleware

Create middleware to handle errors for specific routes:
const errorHandlingMiddleware = async (c, next) => {
  try {
    await next()
  } catch (error) {
    if (error instanceof HTTPException) {
      return error.getResponse()
    }
    
    // Log error
    console.error('Unexpected error:', error)
    
    // Return generic error
    return c.json(
      { error: 'An unexpected error occurred' },
      500
    )
  }
}

app.use('/api/*', errorHandlingMiddleware)

Validation Errors

Handle validation errors from the validator middleware:
import { validator } from 'hono/validator'

app.post(
  '/users',
  validator('json', (value, c) => {
    const data = value as { email: string; age: number }
    
    const errors = []
    
    if (!data.email || !data.email.includes('@')) {
      errors.push('Invalid email')
    }
    
    if (data.age < 18) {
      errors.push('Must be 18 or older')
    }
    
    if (errors.length > 0) {
      return c.json({ errors }, 400)
    }
    
    return data
  }),
  (c) => {
    const user = c.req.valid('json')
    return c.json({ created: true, user })
  }
)

Async Error Handling

Handle errors in async handlers:
app.get('/data', async (c) => {
  try {
    const data = await fetchExternalAPI()
    return c.json(data)
  } catch (error) {
    if (error.code === 'TIMEOUT') {
      throw new HTTPException(504, { 
        message: 'Gateway Timeout' 
      })
    }
    if (error.code === 'NOT_FOUND') {
      throw new HTTPException(404, { 
        message: 'Resource not found' 
      })
    }
    throw new HTTPException(500, { 
      message: 'Failed to fetch data',
      cause: error 
    })
  }
})

Status Codes

Common HTTP status codes for errors:
  • 400 - Bad Request (validation errors)
  • 401 - Unauthorized (authentication required)
  • 403 - Forbidden (insufficient permissions)
  • 404 - Not Found (resource doesn’t exist)
  • 409 - Conflict (resource conflict)
  • 422 - Unprocessable Entity (semantic errors)
  • 429 - Too Many Requests (rate limiting)
  • 500 - Internal Server Error (server errors)
  • 503 - Service Unavailable (temporary issues)

Best Practices

Choose the correct HTTP status code that accurately represents the error condition.
Include clear, actionable error messages that help users understand what went wrong.
Always log errors server-side for debugging, but don’t expose internal details to clients.
Use a global error handler to ensure consistent error response format across your API.
Never include sensitive information like stack traces or internal paths in production error responses.

Build docs developers (and LLMs) love