Construcción de una REST API de alto rendimiento en Go con Fiber: enrutamiento, middleware y despliegue en Docker
1. Introducción — por qué Fiber en 2026
Go Fiber REST API es hoy uno de los stacks más populares para construir servicios de alta carga. Fiber está construido sobre fasthttp —el motor HTTP más rápido para Go—, lo que le otorga ventaja sobre net/http en escenarios con miles de conexiones simultáneas. Según los benchmarks actuales de TechEmpower, Fiber se sitúa de forma constante en el top 5 de frameworks en métricas de serialización JSON y peticiones plaintext.
Por qué elegir Fiber framework 2026:
- Sintaxis familiar para desarrolladores de Express.js — curva de aprendizaje mínima.
- Middleware integrados: logging, CORS, rate limiter, recovery.
- Ecosistema activo: adaptadores para Redis, WebSocket, SSE, gRPC.
- Cero alocaciones en el camino crítico gracias a fasthttp.
2. Arquitectura del proyecto
Antes de escribir código, es importante definir la estructura de carpetas. Utilizamos el enfoque clean architecture lite — sin abstracciones excesivas, pero con una separación clara de capas:
myapi/
├── cmd/
│ └── server/
│ └── main.go # punto de entrada
├── internal/
│ ├── config/ # configuración desde ENV
│ ├── handler/ # manejadores HTTP
│ ├── middleware/ # middleware personalizados
│ ├── repository/ # capa de acceso a BD
│ ├── service/ # lógica de negocio
│ └── model/ # estructuras de datos
├── pkg/
│ ├── database/ # inicialización de pgx y Redis
│ └── validator/ # helpers de validación
├── migrations/ # migraciones SQL
├── Dockerfile
├── docker-compose.yml
└── .env.example
Las capas interactúan estrictamente de arriba hacia abajo: handler → service → repository. Los middleware se inyectan en el enrutador antes que los manejadores.
3. Definición de rutas y grupos: versionado de la API
El versionado correcto es la clave para el soporte a largo plazo. Usamos prefijos /api/v1 y /api/v2 mediante grupos de 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)
}
// Rutas protegidas v1
protected := v1.Group("/admin", middleware.JWTAuth())
protected.Get("/stats", h.GetStats)
}
Los grupos permiten aplicar middleware de forma selectiva: JWTAuth solo se aplica en /admin, sin afectar los endpoints públicos.
4. Implementación de middleware: logging, JWT y rate limiting
4.1 Logging de peticiones
// 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 Autenticación 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 peticiones
Expiration: time.Minute, // por minuto desde una 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. Conexión con PostgreSQL mediante pgx y Redis para caché
5.1 Inicialización de 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 Caché de respuestas con 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 almacena en caché las respuestas GET con un TTL dado
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
}
}
El middleware de caché lee la respuesta de Redis con la clave cache:{URL}. Si no hay caché, ejecuta el manejador y almacena el cuerpo de la respuesta con el TTL indicado — patrón típico de cache-aside para Go API PostgreSQL Redis.
6. Validación de datos de entrada y manejo de errores
Para la validación usamos la librería go-playground/validator. Crearemos un helper centralizado:
// 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
}
Ejemplo de uso en el handler 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. Escritura de pruebas para los handlers
Fiber soporta pruebas a través del método app.Test(), lo que permite prescindir de una conexión de red real:
// 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)
}
Simulamos la capa de servicio mediante testify/mock, aislando el handler de la base de datos. Esto acelera la ejecución de las pruebas y las hace deterministas.
8. Contenedorización: Dockerfile multietapa y 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"]
La construcción multietapa reduce el tamaño final de la imagen de ~300 MB a ~10–15 MB. Los flags -s -w eliminan la información de depuración, comprimiendo adicionalmente el binario.
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:
La condición condition: service_healthy garantiza que el contenedor de la API solo arranque tras los healthchecks exitosos de PostgreSQL y Redis.
9. Fundamentos de CI/CD: construcción automática y publicación de la imagen
Configuraremos un pipeline simple de GitHub Actions que, al hacer push a main, ejecuta las pruebas, construye la imagen y la publica en 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 }}
El job build-and-push solo se ejecuta si las pruebas han pasado, evitando la publicación de una imagen defectuosa. La imagen se etiqueta con el SHA del commit para permitir la vuelta atrás.
10. Conclusión
Hemos construido una API de alto rendimiento en Go completa con Fiber: desde el enrutamiento con versionado hasta el caché con Redis, la contenedorización con Docker y un pipeline CI/CD automatizado. Conclusiones clave:
- Fiber garantiza latencias mínimas gracias a fasthttp y el enfoque zero-alloc.
- Los grupos de rutas y la aplicación selectiva de middleware hacen el código escalable.
- pgx + Redis es una combinación probada para REST APIs en Go con caché a nivel de respuesta.
- El Dockerfile multietapa reduce el tamaño de la imagen en un orden de magnitud, mejorando la seguridad y la velocidad de despliegue.
- GitHub Actions automatiza las pruebas y la publicación sin intervención manual.
El siguiente paso sería agregar distributed tracing (OpenTelemetry), escalado horizontal con Kubernetes y migraciones mediante golang-migrate. Pero la arquitectura descrita ya es capaz de soportar decenas de miles de peticiones por segundo en hardware modesto.
Tecnologías
Etiquetas
Ruslan Ismailov
Desarrollador Senior Web / Backend. Desarrollador senior web/backend con 9 años de experiencia. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservicios, CI/CD. Más sobre mí →