Построение высокопроизводительного REST API на Go с Fiber: маршрутизация, middleware и деплой в Docker
1. Введение — почему Fiber в 2026 году
Go Fiber REST API сегодня — один из самых популярных стеков для построения высоконагруженных сервисов. Fiber построен поверх fasthttp — самого быстрого HTTP-движка для Go, — что даёт ему преимущество перед net/http в сценариях с тысячами одновременных соединений. По данным актуальных бенчмарков TechEmpower, Fiber стабильно входит в топ-5 фреймворков по метрикам JSON-сериализации и plaintext-запросов.
Почему выбирают Fiber framework 2026:
- Синтаксис, знакомый Express.js-разработчикам — минимальный порог входа.
- Встроенные middleware: логирование, CORS, rate limiter, recovery.
- Активная экосистема: адаптеры для Redis, WebSocket, SSE, gRPC.
- Нулевые аллокации на горячем пути благодаря fasthttp.
2. Архитектура проекта
Перед написанием кода важно определить структуру папок. Мы используем подход clean architecture lite — без избыточных абстракций, но с чётким разделением слоёв:
myapi/
├── cmd/
│ └── server/
│ └── main.go # точка входа
├── internal/
│ ├── config/ # конфиг из ENV
│ ├── handler/ # HTTP-обработчики
│ ├── middleware/ # кастомные middleware
│ ├── repository/ # слой работы с БД
│ ├── service/ # бизнес-логика
│ └── model/ # структуры данных
├── pkg/
│ ├── database/ # инициализация pgx и Redis
│ └── validator/ # хелперы валидации
├── migrations/ # SQL-миграции
├── Dockerfile
├── docker-compose.yml
└── .env.example
Слои взаимодействуют строго сверху вниз: handler → service → repository. Middleware инжектируются в маршрутизатор до обработчиков.
3. Определение маршрутов и групп: версионирование API
Правильное версионирование — залог долгосрочной поддержки. Используем префиксы /api/v1 и /api/v2 через группы Fiber:
// internal/handler/routes.go
package handler
import (
"github.com/gofiber/fiber/v2"
"myapi/internal/middleware"
)
func RegisterRoutes(app *fiber.App, h *UserHandler) {
api := app.Group("/api")
v1 := api.Group("/v1", middleware.Logger())
{
users := v1.Group("/users")
users.Get("/", h.ListUsers)
users.Post("/", h.CreateUser)
users.Get("/:id", h.GetUser)
users.Put("/:id", h.UpdateUser)
users.Delete("/:id", h.DeleteUser)
}
// Защищённые маршруты v1
protected := v1.Group("/admin", middleware.JWTAuth())
protected.Get("/stats", h.GetStats)
}
Группы позволяют применять middleware точечно: JWTAuth навешивается только на /admin, не затрагивая публичные эндпоинты.
4. Реализация middleware: логирование, JWT, rate limiting
4.1 Логирование запросов
// internal/middleware/logger.go
package middleware
import (
"github.com/gofiber/fiber/v2"
fiberLogger "github.com/gofiber/fiber/v2/middleware/logger"
)
func Logger() fiber.Handler {
return fiberLogger.New(fiberLogger.Config{
Format: "[${time}] ${status} - ${latency} ${method} ${path}\n",
})
}
4.2 JWT-аутентификация
// internal/middleware/jwt.go
package middleware
import (
"github.com/gofiber/fiber/v2"
jwtware "github.com/gofiber/contrib/jwt"
"os"
)
func JWTAuth() fiber.Handler {
return jwtware.New(jwtware.Config{
SigningKey: jwtware.SigningKey{
Key: []byte(os.Getenv("JWT_SECRET")),
},
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "invalid or expired token",
})
},
})
}
4.3 Rate Limiting
// internal/middleware/ratelimit.go
package middleware
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"time"
)
func RateLimiter() fiber.Handler {
return limiter.New(limiter.Config{
Max: 100, // 100 запросов
Expiration: time.Minute, // в минуту с одного IP
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP()
},
LimitReached: func(c *fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "rate limit exceeded",
})
},
})
}
5. Подключение PostgreSQL через pgx и Redis для кэширования
5.1 Инициализация PostgreSQL (pgx)
// pkg/database/postgres.go
package database
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v5/pgxpool"
)
func NewPostgresPool(ctx context.Context) (*pgxpool.Pool, error) {
dsn := fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s?sslmode=disable",
os.Getenv("POSTGRES_USER"),
os.Getenv("POSTGRES_PASSWORD"),
os.Getenv("POSTGRES_HOST"),
os.Getenv("POSTGRES_PORT"),
os.Getenv("POSTGRES_DB"),
)
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
return nil, fmt.Errorf("pgxpool.New: %w", err)
}
if err = pool.Ping(ctx); err != nil {
return nil, fmt.Errorf("postgres ping: %w", err)
}
return pool, nil
}
5.2 Кэширование ответов с Redis
// pkg/database/redis.go
package database
import (
"context"
"fmt"
"os"
"time"
"github.com/redis/go-redis/v9"
)
func NewRedisClient() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")),
Password: os.Getenv("REDIS_PASSWORD"),
DB: 0,
})
}
// CacheMiddleware кэширует GET-ответы на заданный TTL
func CacheResponse(rdb *redis.Client, ttl time.Duration) fiber.Handler {
return func(c *fiber.Ctx) error {
if c.Method() != "GET" {
return c.Next()
}
key := "cache:" + c.OriginalURL()
cached, err := rdb.Get(context.Background(), key).Bytes()
if err == nil {
c.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSONCharsetUTF8)
return c.Status(fiber.StatusOK).Send(cached)
}
if err := c.Next(); err != nil {
return err
}
rdb.Set(context.Background(), key, c.Response().Body(), ttl)
return nil
}
}
Кэш-миддлвар читает ответ из Redis по ключу cache:{URL}. При промахе выполняет обработчик и сохраняет тело ответа с заданным TTL — типичный паттерн cache-aside для Go API PostgreSQL Redis.
6. Валидация входных данных и обработка ошибок
Для валидации используем библиотеку go-playground/validator. Создадим единый хелпер:
// pkg/validator/validator.go
package validator
import (
"fmt"
"github.com/go-playground/validator/v10"
)
var validate = validator.New()
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
}
func Validate(s any) []ValidationError {
var errors []ValidationError
err := validate.Struct(s)
if err == nil {
return nil
}
for _, e := range err.(validator.ValidationErrors) {
errors = append(errors, ValidationError{
Field: e.Field(),
Message: fmt.Sprintf("failed on tag '%s'", e.Tag()),
})
}
return errors
}
Пример использования в хендлере CreateUser:
// internal/handler/user.go
func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
var req model.CreateUserRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "cannot parse body",
})
}
if errs := validator.Validate(req); errs != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
"errors": errs,
})
}
user, err := h.svc.CreateUser(c.Context(), req)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(user)
}
7. Написание тестов для handler-ов
Fiber поддерживает тестирование через метод app.Test(), что позволяет обходиться без реального сетевого соединения:
// internal/handler/user_test.go
package handler_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"myapi/internal/handler"
"myapi/internal/service/mocks"
)
func TestCreateUser_Success(t *testing.T) {
mockSvc := new(mocks.UserService)
mockSvc.On("CreateUser", mock.Anything, mock.AnythingOfType("model.CreateUserRequest")).
Return(&model.User{ID: 1, Email: "test@example.com"}, nil)
h := handler.NewUserHandler(mockSvc)
app := fiber.New()
app.Post("/users", h.CreateUser)
body, _ := json.Marshal(map[string]string{
"email": "test@example.com",
"password": "secret123",
})
req := httptest.NewRequest(http.MethodPost, "/users", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req, -1)
assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
mockSvc.AssertExpectations(t)
}
Мокируем сервисный слой через testify/mock, изолируя хендлер от базы данных. Это ускоряет прогон тестов и делает их детерминированными.
8. Контейнеризация: многоэтапный Dockerfile и Docker Compose
8.1 Dockerfile
# Dockerfile
# ---- Build stage ----
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server ./cmd/server
# ---- Run stage ----
FROM gcr.io/distroless/static-debian12
WORKDIR /app
COPY --from=builder /app/server .
COPY --from=builder /app/migrations ./migrations
EXPOSE 3000
USER nonroot:nonroot
ENTRYPOINT ["/app/server"]
Многоэтапная сборка уменьшает итоговый образ с ~300 МБ до ~10–15 МБ. Флаги -s -w убирают отладочную информацию, дополнительно сжимая бинарник.
8.2 docker-compose.yml
# docker-compose.yml
version: "3.9"
services:
api:
build: .
container_name: myapi
ports:
- "3000:3000"
env_file: .env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
container_name: myapi_postgres
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: myapi_redis
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
pgdata:
redisdata:
Условие condition: service_healthy гарантирует, что API-контейнер стартует только после успешных healthcheck-ов PostgreSQL и Redis.
9. Основы CI/CD: автосборка и публикация образа
Настроим простой GitHub Actions pipeline, который при пуше в main запускает тесты, собирает образ и пушит его в Docker Hub:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.23"
- name: Run tests
run: go test ./... -race -coverprofile=coverage.out
- name: Upload coverage
uses: codecov/codecov-action@v4
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/myapi:latest
${{ secrets.DOCKER_USERNAME }}/myapi:${{ github.sha }}
Джоб build-and-push выполняется только если тесты прошли, что исключает публикацию сломанного образа. Тегируем образ SHA коммита для возможности отката.
10. Заключение
Мы построили полноценный высокопроизводительный API на Go с использованием Fiber: от маршрутизации с версионированием до кэширования через Redis, контейнеризации с Docker и автоматического CI/CD-пайплайна. Ключевые выводы:
- Fiber обеспечивает минимальные задержки за счёт fasthttp и zero-alloc подхода.
- Группы маршрутов и точечное применение middleware делают код масштабируемым.
- pgx + Redis — проверенная связка для Go REST API с кэшированием на уровне ответов.
- Многоэтапный Dockerfile сокращает размер образа на порядок, улучшая безопасность и скорость деплоя.
- GitHub Actions автоматизирует тестирование и публикацию без ручного вмешательства.
Следующий шаг — добавить distributed tracing (OpenTelemetry), горизонтальное масштабирование через Kubernetes и миграции через golang-migrate. Но уже описанная архитектура выдержит десятки тысяч запросов в секунду на скромном железе.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →