Разработка бэкенда сайта на Go (Fiber)
Fiber — Go-фреймворк, вдохновлённый Express.js. Если разработчик пришёл из Node.js, API покажется знакомым. Внутри — fasthttp вместо стандартного net/http, что даёт ~2x прирост в синтетических тестах на чистом HTTP. В реальных проектах с PostgreSQL и Redis разница меньше, но Fiber всё равно один из быстрейших Go-фреймворков.
Важный нюанс: fasthttp несовместим с net/http middleware. Это означает, что часть Go-экосистемы (например, стандартные OpenTelemetry middleware под net/http) не работает напрямую — нужны адаптеры или Fiber-специфичные пакеты.
Инициализация
package main
import (
"log"
"os"
"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/helmet"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/middleware/limiter"
)
func main() {
app := fiber.New(fiber.Config{
AppName: "MyAPI v1.0",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
BodyLimit: 4 * 1024 * 1024, // 4MB
ErrorHandler: customErrorHandler,
DisableStartupMessage: true,
})
app.Use(recover.New())
app.Use(helmet.New())
app.Use(compress.New(compress.Config{Level: compress.LevelBestSpeed}))
app.Use(cors.New(cors.Config{
AllowOrigins: os.Getenv("ALLOWED_ORIGINS"),
AllowCredentials: true,
AllowHeaders: "Origin, Content-Type, Authorization",
}))
app.Use(logger.New(logger.Config{
Format: "${time} | ${status} | ${latency} | ${method} ${path}\n",
}))
app.Use(limiter.New(limiter.Config{Max: 100, Expiration: 60 * time.Second}))
setupRoutes(app)
log.Fatal(app.Listen(":8080"))
}
Роутинг и группировка
func setupRoutes(app *fiber.App) {
api := app.Group("/api/v1")
// Публичные
api.Post("/auth/login", authHandler.Login)
api.Post("/auth/refresh", authHandler.Refresh)
// С JWT middleware
api.Get("/products", productHandler.List)
api.Get("/products/:id", productHandler.Get)
protected := api.Group("/", jwtMiddleware)
protected.Get("/profile", authHandler.Profile)
admin := api.Group("/admin", jwtMiddleware, roleMiddleware("admin"))
admin.Post("/products", productHandler.Create)
admin.Put("/products/:id", productHandler.Update)
admin.Delete("/products/:id", productHandler.Delete)
}
Handlers
type ProductHandler struct {
svc *ProductService
}
type CreateProductInput struct {
Name string `json:"name" validate:"required,min=2,max=255"`
Price float64 `json:"price" validate:"required,gt=0"`
CategoryID *int `json:"category_id" validate:"omitempty,gt=0"`
}
func (h *ProductHandler) List(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
limit := c.QueryInt("limit", 20)
if limit > 100 { limit = 100 }
catID := 0
if s := c.Query("category_id"); s != "" {
catID, _ = strconv.Atoi(s)
}
products, total, err := h.svc.List(c.Context(), ListParams{
Page: page,
Limit: limit,
CategoryID: catID,
})
if err != nil {
return fiber.ErrInternalServerError
}
return c.JSON(fiber.Map{
"data": products,
"pagination": fiber.Map{"page": page, "limit": limit, "total": total},
})
}
func (h *ProductHandler) Create(c *fiber.Ctx) error {
var input CreateProductInput
if err := c.BodyParser(&input); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if err := validate.Struct(&input); err != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
"errors": formatValidationErrors(err),
})
}
product, err := h.svc.Create(c.Context(), input)
if err != nil {
return mapError(err)
}
return c.Status(fiber.StatusCreated).JSON(product)
}
Кастомный error handler
func customErrorHandler(c *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
var e *fiber.Error
if errors.As(err, &e) {
code = e.Code
}
// Не раскрываем детали 5xx в production
if code >= 500 && os.Getenv("APP_ENV") == "production" {
return c.Status(code).JSON(fiber.Map{"error": "Internal Server Error"})
}
return c.Status(code).JSON(fiber.Map{"error": err.Error()})
}
Загрузка файлов
func (h *UploadHandler) Upload(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "no file provided")
}
if file.Size > 10*1024*1024 {
return fiber.NewError(fiber.StatusRequestEntityTooLarge, "file too large")
}
ext := strings.ToLower(filepath.Ext(file.Filename))
if !slices.Contains([]string{".jpg", ".jpeg", ".png", ".webp"}, ext) {
return fiber.NewError(fiber.StatusBadRequest, "unsupported file type")
}
f, err := file.Open()
if err != nil {
return fiber.ErrInternalServerError
}
defer f.Close()
key := fmt.Sprintf("uploads/%s%s", uuid.New(), ext)
url, err := h.storage.Upload(c.Context(), key, f, file.Header.Get("Content-Type"))
if err != nil {
return fiber.ErrInternalServerError
}
return c.JSON(fiber.Map{"url": url})
}
JWT middleware
package middleware
import (
"strings"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
)
func JWTMiddleware(secret string) fiber.Handler {
return func(c *fiber.Ctx) error {
auth := c.Get("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
return fiber.ErrUnauthorized
}
token, err := jwt.Parse(auth[7:], func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fiber.ErrUnauthorized
}
return []byte(secret), nil
})
if err != nil || !token.Valid {
return fiber.ErrUnauthorized
}
claims := token.Claims.(jwt.MapClaims)
c.Locals("userID", int(claims["sub"].(float64)))
c.Locals("role", claims["role"])
return c.Next()
}
}
Когда fasthttp ограничивает
fasthttp переиспользует объекты request/context для снижения GC-pressure. Это требует осторожности: нельзя захватывать *fiber.Ctx в горутины без c.Copy(). При передаче контекста в async-операции:
func (h *Handler) AsyncProcess(c *fiber.Ctx) error {
// НЕ делайте так — ctx будет переиспользован до завершения горутины
// go func() { h.svc.Process(c) }()
// Делайте копию
cc := c.Copy()
go func() {
h.svc.ProcessAsync(context.Background(), cc.Body())
}()
return c.SendStatus(fiber.StatusAccepted)
}
Сроки разработки
- Настройка + middleware + роуты — 3–5 дней
- Handlers + сервисный слой — 1–2 недели
- Repository + pgx — 3–5 дней
- Auth + кеш — 3–5 дней
- Тесты — 1 неделя
API для сайта: 4–8 недель. Fiber хорошо подходит командам с Node.js-бэкграундом, переходящим на Go, и проектам с экстремальными требованиями к RPS.







