การจัดการ Error
Error as value — philosophy ของ Go
หัวข้อที่มีชื่อว่า “Error as value — philosophy ของ Go”TypeScript ใช้ throw กับ try/catch ส่วน Go ถือว่า error เป็น return value ธรรมดา: ทุกฟังก์ชันที่อาจ fail จะคืน (result, error) คุณต้องเช็ค error อย่างชัดเจน — compiler ไม่ยอมให้เพิกเฉยแบบเงียบ ๆ Echo ต่อยอดแนวคิดนี้แบบธรรมชาติ: handler ที่คืน error ที่ไม่เป็น nil จะ trigger error handler ของ Echo
// 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 ท้ายสุดapp.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 — ส่งต่อ error ไปยัง error handler ของ Echofunc 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 — error ที่รู้จัก HTTP
หัวข้อที่มีชื่อว่า “echo.NewHTTPError — error ที่รู้จัก HTTP”echo.NewHTTPError(statusCode, message) สร้าง *echo.HTTPError เมื่อ handler คืนค่านี้ default error handler ของ Echo จะ serialize เป็น {"message": "..."} พร้อม HTTP status code ที่ถูกต้อง สำหรับ error ธรรมดา Echo จะคืน 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 error พร้อม context
หัวข้อที่มีชื่อว่า “Wrapping error พร้อม context”ห่อ error ด้วย fmt.Errorf("context: %w", err) เสมอ เพื่อให้ error ต้นฉบับถูกเก็บไว้สำหรับการตรวจสอบด้วย errors.Is / errors.As และสำหรับ 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 ด้วย %wfunc (s *BookService) Create(ctx context.Context, req CreateBookRequest) (Book, error) { book, err := s.store.Insert(ctx, req) if err != nil { // %w เก็บ error ต้นฉบับไว้สำหรับ errors.Is / errors.As return Book{}, fmt.Errorf("BookService.Create: %w", err) } return book, nil}
// Handler — ตรวจสอบชนิด errorfunc createBook(c echo.Context) error { book, err := svc.Create(c.Request().Context(), req) if err != nil { // errors.Is เดิน 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
หัวข้อที่มีชื่อว่า “Central error handler”Echo ให้คุณแทน default error handler ด้วย e.HTTPErrorHandler ตรงนี้คือจุดที่คุณจะ log error, แปลง domain error เป็น HTTP code และทำให้ response envelope สม่ำเสมอ
// Express — error middleware (4 argument, ต้องอยู่ท้ายสุด)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 — ใช้ status code ของตัวเอง _ = c.JSON(he.Code, map[string]any{ "message": he.Message, }) return }
// Error ที่ไม่รู้จัก — log และคืน 500 c.Logger().Errorf("unhandled error: %v", err) _ = c.JSON(http.StatusInternalServerError, map[string]any{ "message": "internal server error", })}
// ลงทะเบียนใน main():e.HTTPErrorHandler = customErrorHandlerรันบนเครื่องของคุณ — ต้องการ Echo module และ network port