API yang Production-Ready
Membuat REST API yang berjalan di local itu mudah. Tapi membuat API yang production-ready - yang handle errors gracefully, punya proper authentication, logging, dan monitoring - itu tantangan yang berbeda. Dari pengalaman saya membangun API untuk berbagai startup di Bandung, berikut best practices yang saya ikuti.
Saya akan menggunakan framework Echo karena sudah terbukti reliable dan performa-tinggi di production.
Project Structure
Struktur project yang clean adalah fondasi dari API yang maintainable:
api/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handler/
│ │ ├── user.go
│ │ └── order.go
│ ├── middleware/
│ │ ├── auth.go
│ │ ├── logging.go
│ │ └── ratelimit.go
│ ├── model/
│ │ ├── user.go
│ │ └── order.go
│ ├── repository/
│ │ ├── user.go
│ │ └── order.go
│ └── service/
│ ├── user.go
│ └── order.go
├── pkg/
│ ├── response/
│ └── validator/
├── migrations/
├── config/
└── docker-compose.ymlPattern ini memisahkan concerns dengan jelas: handler untuk HTTP, service untuk business logic, dan repository untuk data access.
Structured Error Handling
Error handling yang konsisten adalah kunci API yang profesional:
// Custom error types
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
}
func (e *AppError) Error() string {
return e.Message
}
// Error constructors
func NotFound(msg string) *AppError {
return &AppError{Code: 404, Message: msg}
}
func BadRequest(msg string) *AppError {
return &AppError{Code: 400, Message: msg}
}
func InternalError(msg string) *AppError {
return &AppError{Code: 500, Message: msg}
}
// Handler dengan error handling yang clean
func (h *UserHandler) GetUser(c echo.Context) error {
id := c.Param("id")
user, err := h.service.GetUserByID(c.Request().Context(), id)
if err != nil {
switch {
case errors.Is(err, ErrUserNotFound):
return NotFound("User tidak ditemukan")
default:
return InternalError("Gagal mengambil data user")
}
}
return c.JSON(200, map[string]interface{}{
"status": "success",
"data": user,
})
}Middleware Stack
Middleware adalah tempat kita handle cross-cutting concerns. Berikut middleware stack yang saya gunakan di hampir semua project:
func SetupMiddleware(e *echo.Echo) {
// Recovery middleware
e.Use(middleware.Recover())
// CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://*.example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
}))
// Custom logging
e.Use(LoggingMiddleware)
// Rate limiting
e.Use(RateLimitMiddleware(100, time.Minute))
// Request ID
e.Use(RequestIDMiddleware)
}
func LoggingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c)
log.Info().
Str("method", c.Request().Method).
Str("path", c.Request().URL.Path).
Int("status", c.Response().Status).
Dur("duration", time.Since(start)).
Str("request_id", c.Response().Header().Get(echo.HeaderXRequestID)).
Msg("request completed")
return err
}
}Database Connection Pool
Untuk database, saya selalu konfigurasi connection pool dengan benar. Ini sangat berpengaruh terhadap performa di production:
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)Kesimpulan
Build REST API yang production-ready butuh attention terhadap detail. Dengan struktur yang clean, error handling yang konsisten, dan middleware yang tepat, API Go kamu akan siap untuk scale. Kalau kamu butuh bantuan dalam membangun API untuk bisnis kamu, feel free untuk menghubungi saya.