Error Handling
Errors as values — the Go philosophy
Section titled “Errors as values — the Go philosophy”TypeScript uses throw and try/catch. Go treats errors as ordinary return values: every function that can fail returns (result, error). You check the error explicitly — the compiler will not let you ignore it silently. Echo extends this naturally: a handler that returns a non-nil error triggers Echo’s error handler.
// TypeScript / Express — throw-basedasync function getBook(id: string): Promise<Book> { const book = await db.findOne(id); if (!book) throw new NotFoundException('book not found'); return book;}
// Express error middleware at the bottomapp.use((err, req, res, next) => { const status = err.status ?? 500; res.status(status).json({ message: err.message });});// Go / Echo — return-basedfunc findBook(ctx context.Context, id string) (Book, error) { book, err := store.FindByID(ctx, id) if err != nil { return Book{}, fmt.Errorf("findBook: %w", err) } return book, nil}
// Handler — propagate the error to Echo's error handlerfunc getBook(c echo.Context) error { book, err := findBook(c.Request().Context(), c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusNotFound, err.Error()) } return c.JSON(http.StatusOK, book)}echo.NewHTTPError — HTTP-aware errors
Section titled “echo.NewHTTPError — HTTP-aware errors”echo.NewHTTPError(statusCode, message) creates an *echo.HTTPError. When a handler returns one, Echo’s default error handler serializes it as {"message": "..."} with the correct HTTP status code. For bare error values, Echo returns 500 Internal Server Error.
// 400return echo.NewHTTPError(http.StatusBadRequest, "invalid id format")
// 401return echo.NewHTTPError(http.StatusUnauthorized, "missing token")
// 404return echo.NewHTTPError(http.StatusNotFound, "book not found")
// 409return echo.NewHTTPError(http.StatusConflict, "book already exists")Wrapping errors with context
Section titled “Wrapping errors with context”Always wrap errors using fmt.Errorf("context: %w", err) so the original error is preserved for errors.Is / errors.As checks and for structured logging.
// TypeScript — wrapping with causethrow new Error('failed to save book', { cause: originalError });
// NestJS — HTTP-specificthrow new InternalServerErrorException('failed to save book');// Go — wrapping with %wfunc (s *BookService) Create(ctx context.Context, req CreateBookRequest) (Book, error) { book, err := s.store.Insert(ctx, req) if err != nil { // %w preserves the original error for errors.Is / errors.As return Book{}, fmt.Errorf("BookService.Create: %w", err) } return book, nil}
// In the handler — check error typefunc createBook(c echo.Context) error { book, err := svc.Create(c.Request().Context(), req) if err != nil { // errors.Is walks the wrap chain if errors.Is(err, store.ErrDuplicate) { return echo.NewHTTPError(http.StatusConflict, "book already exists") } return echo.NewHTTPError(http.StatusInternalServerError, "failed to create book") } return c.JSON(http.StatusCreated, book)}Central error handler
Section titled “Central error handler”Echo lets you replace its default error handler with e.HTTPErrorHandler. This is where you log errors, translate domain errors to HTTP codes, and ensure a consistent response envelope.
// Express — error middleware (4-arg function, must be last)app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err); if (err instanceof HttpException) { return res.status(err.status).json({ message: err.message }); } res.status(500).json({ message: 'internal server error' });});// Echo — custom HTTP error handlerfunc customErrorHandler(err error, c echo.Context) { var he *echo.HTTPError if errors.As(err, &he) { // Echo HTTPError — use its status code _ = c.JSON(he.Code, map[string]any{ "message": he.Message, }) return }
// Unknown error — log it, return 500 c.Logger().Errorf("unhandled error: %v", err) _ = c.JSON(http.StatusInternalServerError, map[string]any{ "message": "internal server error", })}
// Register in main():e.HTTPErrorHandler = customErrorHandlerRun this locally — it needs the Echo module and a network port.