Skip to content

Un enrutador HTTP para Go ultra potente inspirado en Django, con características avanzadas para desarrollo rápido de APIs RESTful.

License

Notifications You must be signed in to change notification settings

sazardev/mora-router

MoraRouter

Un enrutador HTTP para Go ultra potente inspirado en Django, con características avanzadas para desarrollo rápido de APIs RESTful.

✨ Características

  • Enrutamiento avanzado: Parámetros tipados, expresiones regulares, wildcards y comodines
  • Middleware incorporado: Logging, recovery, CORS, cache, rate limiting, métricas, debug
  • Recursos RESTful: Generación automática de rutas CRUD para recursos
  • Validación de datos: Binding automático de JSON/XML/Form con validación de campos
  • OpenAPI/Swagger: Generación automática de documentación
  • Herramientas de testing: Cliente de pruebas para simplificar tests de API
  • Generador de código: Scaffolding de controladores, modelos y recursos
  • Macros de rutas: Patrones reutilizables para definición rápida de rutas
  • Internacionalización: i18n de rutas según cabeceras Accept-Language
  • SPA y assets: Soporte para aplicaciones de página única y archivos estáticos
  • GraphQL y WebSockets: Integración sencilla con otros protocolos
  • Hot Reload: Recarga automática de rutas sin reiniciar el servidor
  • Inspector de rutas: UI web para explorar y probar rutas en runtime
  • Respuestas flexibles: Soporte para JSON, XML, CSV, HTML y respuestas negociadas

🚀 Instalación

go get -u github.com/yourusername/mora-router

📖 Guía Rápida

Router básico

package main

import (
    "log"
    "net/http"
    "time"
    
    "github.com/yourusername/mora-router/router"
)

func main() {
    // Crear router con middlewares y características
    r := router.New(
        router.WithLogging(),      // Log de peticiones
        router.WithRecovery(),     // Recuperación de panic
        router.WithCORS("*"),      // CORS configurado
        router.WithSwagger(),      // Endpoint OpenAPI en /openapi.json
        router.WithDebug(),        // Inspector en /_mora/inspector
        router.WithMetrics(),      // Métricas en /metrics
        router.WithCache(time.Minute), // Cacheo de respuestas
    )
    
    // Ruta simple con respuesta JSON
    r.Get("/hello/:name", func(w http.ResponseWriter, req *http.Request, p router.Params) {
        router.JSON(w, http.StatusOK, map[string]interface{}{
            "message": "¡Hola, " + p["name"] + "!",
            "timestamp": time.Now(),
        })
    })
    
    // Iniciar servidor
    log.Println("Servidor iniciado en :8080")
    log.Println("Inspector disponible en http://localhost:8080/_mora/inspector")
    http.ListenAndServe(":8080", r)
}

Parámetros avanzados en rutas

MoraRouter soporta varios tipos de parámetros en rutas:

// Parámetros básicos
r.Get("/users/:id", handler)                 // /users/123

// Expresiones regulares embebidas
r.Get("/posts/:year(\\d{4})/:month(\\d{2})", handler)  // /posts/2023/05

// Validación con sintaxis alternativa
r.Get("/products/{code:[A-Z]{3}\\d{4}}", handler)  // /products/ABC1234

// Parámetros comodín (capturar resto de la ruta)
r.Get("/files/*filepath", handler)  // /files/docs/manual.pdf

Recursos RESTful automáticos

// UserController implementa un controlador RESTful
type UserController struct {
    router.DefaultController
}

// Show muestra un usuario por ID
func (c UserController) Show(w http.ResponseWriter, r *http.Request, p router.Params) {
    id := p["id"]
    router.JSON(w, http.StatusOK, map[string]interface{}{
        "id": id,
        "name": "Usuario " + id,
    })
}

// En func main():
r.Resource("/users", UserController{})

// Esto registra automáticamente:
// GET /users          -> Index()  - Listar todos
// GET /users/:id      -> Show()   - Mostrar uno
// POST /users         -> Create() - Crear nuevo
// PUT /users/:id      -> Update() - Actualizar
// DELETE /users/:id   -> Delete() - Eliminar

Validación y binding automático

// Con JSON
type CreateUserInput struct {
    Name  string `json:"name" validate:"required,min=3"`
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"min=18"`
}

r.Post("/users", router.BindJSON(func(w http.ResponseWriter, r *http.Request, p router.Params, input CreateUserInput) {
    // input ya está validado y procesado
    router.JSON(w, http.StatusCreated, map[string]interface{}{
        "message": "Usuario creado",
        "user": input,
    })
}))

// Con formularios y archivos
type UploadProfileInput struct {
    Name      string      `form:"name" validate:"required"`
    Email     string      `form:"email" validate:"required,email"`
    Avatar    *router.FormFile `form:"avatar"`
}

r.Post("/profile", router.BindForm(func(w http.ResponseWriter, r *http.Request, p router.Params, form *router.Form, input UploadProfileInput) {
    if form.HasErrors() {
        router.JSON(w, http.StatusBadRequest, form.GetErrors())
        return
    }
    
    // Guardar archivo si existe
    filePath := ""
    if input.Avatar != nil {
        filePath, _ = form.SaveFile("avatar", "")
    }
    
    router.JSON(w, http.StatusCreated, map[string]interface{}{
        "message": "Perfil actualizado",
        "avatar_path": filePath,
    })
}))

Middleware y opciones

// Middleware personalizado
func AuthMiddleware(next router.HandlerFunc) router.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request, p router.Params) {
        token := r.Header.Get("Authorization")
        if token == "" {
            router.Error(w, http.StatusUnauthorized, "Token requerido")
            return
        }
        // Aquí validaríamos el token...
        next(w, r, p)
    }
}

// Aplicar a rutas específicas
r.With(AuthMiddleware).Get("/admin", adminHandler)

// Aplicar a un grupo de rutas
admin := r.Group("/admin")
admin.Use(AuthMiddleware)
admin.Get("/dashboard", dashboardHandler)
admin.Get("/users", usersHandler)

// JWT integrado
r.With(router.WithJWT("secret-key"))
r.With(router.RequireRole("admin")).Get("/admin", adminHandler)

Grupos de rutas y versionado API

// Agrupar rutas bajo un prefijo
v1 := r.Group("/api/v1")
v1.Get("/users", listUsersV1)
v1.Get("/products", listProductsV1)

v2 := r.Group("/api/v2")
v2.Get("/users", listUsersV2)

// Versionado automático por cabecera
r := router.New(router.WithAPIVersioning("X-API-Version", "1"))
// Las rutas se reescriben automáticamente según la cabecera

Uso de macros de rutas

// Registrar una macro personalizada
router.RegisterMacro("paginated", "/:page(\\d+)", []string{"GET"}, rateLimitMiddleware(100, time.Minute))

// Usar la macro
r.UseMacro("/users", "paginated", func(w http.ResponseWriter, req *http.Request, p router.Params) {
    page := p["page"] // La página viene del parámetro :page
    router.JSON(w, http.StatusOK, map[string]string{"page": page})
})

// Crear API CRUD con macros predefinidas
r.UseMacro("/products", "list", listProductsHandler)
r.UseMacro("/products", "detail", getProductHandler)
r.UseMacro("/products", "create", createProductHandler)
r.UseMacro("/products", "update", updateProductHandler)
r.UseMacro("/products", "delete", deleteProductHandler)

Respuestas en múltiples formatos

// Cliente solicitando diferentes formatos
// Render flexible
render := router.NewRender()

// JSON (con indentación)
render.JSON(w, http.StatusOK, data)

// XML
render.XML(w, http.StatusOK, data)

// CSV desde slice de structs
render.CSV(w, http.StatusOK, users)

// HTML con plantillas
render.HTMLTemplates = template.Must(template.ParseGlob("templates/*.html"))
render.HTML(w, http.StatusOK, "user.html", user)

// Negociación de contenido
render.Negotiate(w, r, http.StatusOK, data)

Hot Reload

// Recarga automática de rutas desde archivo JSON
r := router.New(router.WithHotReload("routes.json", 5 * time.Second))

// Archivo routes.json:
// {
//   "routes": [
//     { "method": "GET", "pattern": "/products", "name": "products.list" },
//     { "method": "GET", "pattern": "/products/:id", "name": "products.show" }
//   ],
//   "groups": {
//     "api": "/api"
//   }
// }

Testing simplificado

func TestUserAPI(t *testing.T) {
    r := router.New()
    r.Resource("/users", UserController{})
    
    client := router.NewTestClient(r)
    
    // Autenticación para todas las peticiones
    client.WithAuth("test-token")
    
    // GET /users/42
    resp := client.Get("/users/42")
    if !resp.IsOK() {
        t.Errorf("Expected 200 status, got %d", resp.Status())
    }
    
    // Deserializar respuesta JSON
    var user map[string]interface{}
    resp.JSON(&user)
    
    // POST /users con payload JSON
    createResp := client.Post("/users", map[string]interface{}{
        "name": "Nuevo Usuario",
        "email": "[email protected]",
        "age": 25,
    })
    
    if !createResp.IsCreated() {
        t.Errorf("Expected 201 status, got %d", createResp.Status())
    }
}

Generador de código

// Generar controlador
gen := router.NewRouteGenerator(r)
controllerCode, _ := gen.GenerateController("product")

// Generar modelo
fields := map[string]string{
    "name": "string",
    "price": "float64",
    "stock": "int",
}
modelCode, _ := gen.GenerateModel("product", fields)

// Generar pruebas
endpoints := []string{
    "GET /products",
    "GET /products/:id",
    "POST /products",
}
testCode, _ := gen.GenerateTests("Product", endpoints)

Inspector de rutas

El inspector web de rutas está disponible en /_mora/inspector cuando se activa con router.WithDebug(). Proporciona:

  • Lista de todas las rutas registradas
  • Información detallada de rutas, parámetros y métodos
  • Consola para probar peticiones en tiempo real
  • Información de depuración sobre el router

Rendimiento y métricas

// Añadir métricas
r := router.New(router.WithMetrics())

// Endpoint /metrics con formato compatible con Prometheus
// Ejemplo de métrica:
// http_handler_latency_seconds_average 0.002345
// http_handler_requests_total 42

WebSockets

// Requiere gorilla/websocket
import "github.com/gorilla/websocket"

// Activar soporte WebSocket con gorilla
r := router.New(router.WithGorillaWebSocket())

// Chat simple
r := router.New(
    router.WithGorillaWebSocket(),
    router.WithChatRoom("/chat")
)
// Accesible en http://localhost:8080/chat-ui

// WebSocket handler personalizado
r := router.New(
    router.WithGorillaWebSocket(),
    router.WithWebSocketHandler(router.WebSocketConfig{
        Path: "/ws",
        // Configuraciones opcionales
        PingInterval: 30, // segundos
        MaxMessageSize: 4096, // bytes
        AllowedOrigins: []string{"example.com"}, // orígenes permitidos
        // Manejadores
        MessageHandler: func(conn *router.WebSocketConnection, msg []byte) {
            // Procesar mensaje y enviar respuesta
            conn.SendJSON(map[string]interface{}{
                "echo": string(msg),
                "time": time.Now(),
            })
        },
        OnConnect: func(conn *router.WebSocketConnection, req *http.Request, p router.Params) {
            log.Printf("Nueva conexión: %s", conn.ID)
        },
    })
)

// Salas de chat dinámicas
r := router.New(
    router.WithGorillaWebSocket(),
    router.WithRoomProvider("/api/rooms", router.WebSocketRoomOption{
        MaxConnections: 100,
        MessageHandler: func(conn *router.WebSocketConnection, msg []byte) {
            // Retransmitir mensaje a todos en la sala
            conn.Hub.Broadcast(msg)
        },
    })
)

// Soporte para múltiples endpoints WebSocket
r := router.New(
    router.WithGorillaWebSocket(),
    router.WithWebSockets(map[string]router.WebSocketHandlerFunc{
        "/chat": chatHandler,
        "/notifications": notificationsHandler,
        "/events": eventsHandler,
    })
)

🌟 Ejemplo completo

Revisa el directorio examples/resource-demo para ver una API completa implementada con MoraRouter.

📚 Documentación completa

Para más información sobre todas las características y opciones avanzadas, consulta la documentación completa.

📄 Licencia

MIT

About

Un enrutador HTTP para Go ultra potente inspirado en Django, con características avanzadas para desarrollo rápido de APIs RESTful.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Languages