fiber
Definitive guidelines for writing high-performance, secure, and maintainable Fiber applications in Go. Focuses on context immutability, robust error handling, and modular design.
خود اسکیل
# Fiber Best Practices
Fiber is a high-performance, Express-inspired web framework for Go, built on `fasthttp`. These rules ensure we leverage Fiber's speed and maintainability while avoiding common pitfalls.
## 1. Core Principle: `fiber.Ctx` Immutability
**NEVER** retain references to `fiber.Ctx` or any data extracted from it (like `c.Params`, `c.Query`, `c.Body`) after the handler returns. `fiber.Ctx` values are reused across requests, making their contents ephemeral. Always copy data if you need to store it.
❌ BAD: Retaining ephemeral data
```go
package handlers
import (
"github.com/gofiber/fiber/v2"
"log"
)
// This struct will hold a reference to the ephemeral string
type UserRequest struct {
UserID string
}
func GetUserBad(c *fiber.Ctx) error {
// DANGER: c.Params("id") returns an ephemeral string.
// Storing it directly will lead to data corruption in future requests.
req := &UserRequest{
UserID: c.Params("id"),
}
// Simulating async processing or storage
go func() {
// By the time this goroutine runs, c.Params("id") might have changed
log.Printf("Processing user ID: %s", req.UserID) // Will likely be wrong
}()
return c.SendString("Request received (bad example)")
}
```
✅ GOOD: Copying ephemeral data
```go
package handlers
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils" // Use Fiber's utility for efficient copying
"log"
)
// This struct holds a safe copy of the string
type UserRequest struct {
UserID string
}
func GetUserGood(c *fiber.Ctx) error {
// ALWAYS copy data from c.Params, c.Query, c.Body, etc.
userID := utils.CopyString(c.Params("id"))
req := &UserRequest{
UserID: userID,
}
// Simulating async processing or storage
go func() {
// This is safe; userID is a distinct copy
log.Printf("Processing user ID: %s", req.UserID) // Will always be correct
}()
return c.SendString("Request received (good example)")
}
```
## 2. Code Organization and Structure
Organize your application into logical layers: `main`, `config`, `router`, `middleware`, `handlers`, `services`, `repository`, `models`, `utils`.
```
.
├── cmd/api/main.go # Application entry point
├── config/config.go # Application configuration loading
├── internal/
│ ├── handlers/ # HTTP request handlers
│ │ └── user_handler.go
│ ├── middleware/ # Global and route-specific middleware
│ │ └── security.go
│ ├── models/ # Data structures (structs for DB, JSON, etc.)
│ │ └── user.go
│ ├── repository/ # Database interaction logic
│ │ └── user_repo.go
│ ├── router/ # Centralized route definitions
│ │ └── router.go
│ └── services/ # Business logic layer
│ └── user_service.go
└── pkg/
└── utils/ # Reusable utility functions
└── response.go
```
## 3. Security Best Practices
Always apply essential security middleware globally.
```go
package middleware
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/csrf"
"github.com/gofiber/fiber/v2/middleware/helmet"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/utils"
)
// FiberMiddleware provides essential security and utility middleware.
func FiberMiddleware(app *fiber.App) {
app.Use(
// Recover from panics to prevent server crashes
recover.New(),
// Add security headers
helmet.New(),
// Enable CORS for frontend integration
cors.New(),
// Protect against CSRF attacks
csrf.New(csrf.Config{
KeyLookup: "header:X-Csrf-Token",
CookieName: "__Host-csrf_",
CookieSameSite: "Strict",
Expiration: 3 * time.Hour,
KeyGenerator: utils.UUID,
}),
// Rate limit requests to prevent abuse
limiter.New(limiter.Config{
Max: 20,
Expiration: 30 * time.Second,
// KeyGenerator: func(c *fiber.Ctx) string { return c.IP() }, // Customize if needed
}),
// Compress responses
compress.New(),
// Request logging
logger.New(),
)
}
```
## 4. Error Handling
Implement a centralized error handler and use custom error types for clarity.
❌ BAD: Inconsistent error responses
```go
// handler.go
func GetUser(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID is required"})
}
// ... database error
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Database error"})
}
```
✅ GOOD: Centralized error handling with custom errors
```go
package utils
import "github.com/gofiber/fiber/v2"
// AppError represents a custom application error
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Err error `json:"-"` // Original error for logging, not exposed to client
}
func (e *AppError) Error() string {
if e.Err != nil {
return e.Message + ": " + e.Err.Error()
}
return e.Message
}
// NewAppError creates a new AppError
func NewAppError(code int, message string, err error) *AppError {
return &AppError{
Code: code,
Message: message,
Err: err,
}
}
// GlobalErrorHandler handles all errors in the application
func GlobalErrorHandler(c *fiber.Ctx, err error) error {
// Default error response
code := fiber.StatusInternalServerError
message := "Internal Server Error"
if e, ok := err.(*AppError); ok {
code = e.Code
message = e.Message
// Log the original error for debugging
if e.Err != nil {
c.App().Logger().Errorf("AppError: %s, Original: %v", e.Message, e.Err)
}
} else if e, ok := err.(*fiber.Error); ok {
code = e.Code
message = e.Message
} else {
// Log unexpected errors
c.App().Logger().Errorf("Unhandled error: %v", err)
}
return c.Status(code).JSON(fiber.Map{
"code": code,
"message": message,
})
}
```
```go
// In your main.go or router setup:
app := fiber.New(fiber.Config{
ErrorHandler: utils.GlobalErrorHandler,
})
// In your handler:
func GetUser(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return utils.NewAppError(fiber.StatusBadRequest, "User ID is required", nil)
}
// Simulate a service call that might return an error
user, err := userService.GetUserByID(id)
if err != nil {
// Wrap service errors into AppError for consistent client response
return utils.NewAppError(fiber.StatusInternalServerError, "Failed to retrieve user", err)
}
return c.JSON(user)
}
```
## 5. API Design: RESTful & Consistent Responses
Design your APIs following REST principles and ensure consistent JSON response structures.
❌ BAD: Inconsistent response formats
```go
// GET /users/:id
// Success: {"id": "123", "name": "John Doe"}
// Error: "User not found" (plain text)
// POST /users
// Success: "User created" (plain text)
// Error: {"message": "Invalid input"} (JSON)
```
✅ GOOD: Standardized JSON responses
```go
package utils
// SuccessResponse for successful API calls
type SuccessResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// SendSuccess sends a standardized success response
func SendSuccess(c *fiber.Ctx, status int, message string, data interface{}) error {
return c.Status(status).JSON(SuccessResponse{
Status: status,
Message: message,
Data: data,
})
}
```
```go
// In your handler:
func GetUser(c *fiber.Ctx) error {
id := c.Params("id")
user, err := userService.GetUserByID(id)
if err != nil {
// Error handling via GlobalErrorHandler
return utils.NewAppError(fiber.StatusNotFound, "User not found", err)
}
return utils.SendSuccess(c, fiber.StatusOK, "User retrieved successfully", user)
}
func CreateUser(c *fiber.Ctx) error {
user := new(models.User)
if err := c.BodyParser(user); err != nil {
return utils.NewAppError(fiber.StatusBadRequest, "Invalid request body", err)
}
// ... validation and service call
createdUser, err := userService.CreateUser(user)
if err != nil {
return utils.NewAppError(fiber.StatusInternalServerError, "Failed to create user", err)
}
return utils.SendSuccess(c, fiber.StatusCreated, "User created successfully", createdUser)
}
```
## 6. Testing Approaches
Use `httptest` and Fiber's test utilities for robust, table-driven unit tests. Inject dependencies to enable easy mocking.
```go
package handlers_test
import (
"bytes"
"encoding/json"
"errors"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"your_module/internal/handlers"
"your_module/internal/models"
"your_module/internal/services" // Mock this
"your_module/pkg/utils"
)
// MockUserService implements the UserService interface for testing
type MockUserService struct {
GetUserByIDFunc func(id string) (*models.User, error)
CreateUserFunc func(user *models.User) (*models.User, error)
}
func (m *MockUserService) GetUserByID(id string) (*models.User, error) {
return m.GetUserByIDFunc(id)
}
func (m *MockUserService) CreateUser(user *models.User) (*models.User, error) {
return m.CreateUserFunc(user)
}
func TestGetUser(t *testing.T) {
app := fiber.New(fiber.Config{
ErrorHandler: utils.GlobalErrorHandler, // Use global error handler for consistency
})
// Setup mock service
mockService := &MockUserService{}
userHandler := handlers.NewUserHandler(mockService) // Inject mock service
app.Get("/users/:id", userHandler.GetUser)
tests := []struct {
name string
userID string
mockReturnUser *models.User
mockReturnErr error
expectedStatus int
expectedBody string
}{
{
name: "Success",
userID: "1",
mockReturnUser: &models.User{ID: "1", Name: "Test User"},
mockReturnErr: nil,
expectedStatus: fiber.StatusOK,
expectedBody: `{"status":200,"message":"User retrieved successfully","data":{"id":"1","name":"Test User"}}`,
},
{
name: "Not Found",
userID: "2",
mockReturnUser: nil,
mockReturnErr: errors.New("user not found"),
expectedStatus: fiber.StatusNotFound,
expectedBody: `{"code":404,"message":"User not found"}`,
},
{
name: "Invalid ID",
userID: "", // Handled by handler logic
mockReturnUser: nil,
mockReturnErr: nil, // This won't be called if ID is empty
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"code":400,"message":"User ID is required"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockService.GetUserByIDFunc = func(id string) (*models.User, error) {
assert.Equal(t, tt.userID, id) // Ensure correct ID is passed
return tt.mockReturnUser, tt.mockReturnErr
}
req := httptest.NewRequest(fiber.MethodGet, "/users/"+tt.userID, nil)
resp, _ := app.Test(req, -1) // -1 disables request latency
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
assert.JSONEq(t, tt.expectedBody, string(body))
})
}
}
```
نصبش چطور است
1. Open the tool and start a new conversation, project or template. 2. Paste the text below wherever the tool keeps standing instructions — usually called system prompt, instructions, or persona. 3. Save it so it applies to every run rather than a single message.