ข้ามไปยังเนื้อหา

การจัดการ Error

TypeScript ใช้ throw กับ try/catch ส่วน Go ถือว่า error เป็น return value ธรรมดา: ทุกฟังก์ชันที่อาจ fail จะคืน (result, error) คุณต้องเช็ค error อย่างชัดเจน — compiler ไม่ยอมให้เพิกเฉยแบบเงียบ ๆ Echo ต่อยอดแนวคิดนี้แบบธรรมชาติ: handler ที่คืน error ที่ไม่เป็น nil จะ trigger error handler ของ Echo

TypeScript
// TypeScript / Express — throw-based
async 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 bottom
app.use((err, req, res, next) => {
const status = err.status ?? 500;
res.status(status).json({ message: err.message });
});
Go
// Go / Echo — return-based
func 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 handler
func 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(statusCode, message) สร้าง *echo.HTTPError (ใน v5 message เป็น string ธรรมดา) เมื่อ handler คืนค่านี้ default error handler ของ Echo จะ serialize เป็น {"message": "..."} พร้อม HTTP status code ที่ถูกต้อง สำหรับ error ธรรมดา Echo จะคืน 500 Internal Server Error

error ตัวไหนก็ตามที่ implement StatusCode() int จะได้รับการปฏิบัติแบบเดียวกัน — error สำเร็จรูป (echo.ErrNotFound, echo.ErrUnauthorized, …) ทำงานด้วยวิธีนี้ ถ้าอยากอ่าน status กลับออกมาจาก error ใด ๆ ใช้ echo.StatusCode(err) ซึ่งจะคืน 0 เมื่อ error นั้นไม่มี status

// 400
return echo.NewHTTPError(http.StatusBadRequest, "invalid id format")
// 401
return echo.NewHTTPError(http.StatusUnauthorized, "missing token")
// 404
return echo.NewHTTPError(http.StatusNotFound, "book not found")
// 409
return echo.NewHTTPError(http.StatusConflict, "book already exists")

ห่อ error ด้วย fmt.Errorf("context: %w", err) เสมอ เพื่อให้ error ต้นฉบับถูกเก็บไว้สำหรับการตรวจสอบด้วย errors.Is / errors.As และสำหรับ structured logging

TypeScript
// TypeScript — wrapping with cause
throw new Error('failed to save book', { cause: originalError });
// NestJS — HTTP-specific
throw new InternalServerErrorException('failed to save book');
Go
// Go — wrapping with %w
func (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 type
func 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)
}

Echo ให้คุณแทน default error handler ด้วย e.HTTPErrorHandler ตรงนี้คือจุดที่คุณจะ log error, แปลง domain error เป็น HTTP code และทำให้ response envelope สม่ำเสมอ ตัว default เป็น factory — echo.DefaultHTTPErrorHandler(exposeError bool) — และมัน ไม่ log ให้ การ log เป็นหน้าที่ของคุณ (หรือของ middleware)

TypeScript
// 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' });
});
Go
// Echo — custom HTTP error handler (v5: context first, error second)
func customErrorHandler(c *echo.Context, err error) {
// Never write twice: a middleware may already have sent a response.
if resp, uerr := echo.UnwrapResponse(c.Response()); uerr == nil && resp.Committed {
return
}
// Anything carrying an HTTP status (echo.NewHTTPError, echo.ErrNotFound, ...)
if code := echo.StatusCode(err); code != 0 {
var he *echo.HTTPError
msg := http.StatusText(code)
if errors.As(err, &he) {
msg = he.Message
}
_ = c.JSON(code, map[string]any{"message": msg})
return
}
// Unknown error — log it (slog), return 500
c.Logger().Error("unhandled error", "err", err)
_ = c.JSON(http.StatusInternalServerError, map[string]any{
"message": "internal server error",
})
}
// Register in main():
e.HTTPErrorHandler = customErrorHandler

สองจุดของ v5 ที่ควรจำ: c.Response() ตอนนี้เป็น http.ResponseWriter ธรรมดา ข้อมูลภายในของ Echo (Status, Size, Committed) จึงต้องดึงผ่าน echo.UnwrapResponse และ errors.As(err, &he) จับได้เฉพาะ error ที่สร้างจาก echo.NewHTTPError เท่านั้น — ค่า echo.Err* สำเร็จรูปเป็นคนละ type (ไม่ export) นี่คือเหตุผลที่ handler ถาม echo.StatusCode ก่อน

รันบนเครื่องของคุณ — ต้องการ Echo module และ network port

เกิดอะไรขึ้นเมื่อ Echo handler คืน bare error (ที่ไม่ใช่ HTTPError)?
format string verb ใดที่เก็บ error ต้นฉบับไว้สำหรับการตรวจสอบด้วย errors.Is?
ลงทะเบียน custom central error handler ใน Echo อย่างไร?
ใน Echo v5 จะอ่าน HTTP status ที่ติดมากับ error ใด ๆ (รวมถึง echo.ErrNotFound) ได้อย่างไร?