Routes

Il cuore di ogni applicazione web: gestione elegante di URL e richieste HTTP

NovitΓ : Flux introduce una sintassi rivoluzionaria per le route: definisci endpoint con codice inline, senza bisogno di controller separati per logica semplice.

Route Base

In Flux, le route si definiscono nel file app/routes.flux:

# Route semplice
route "/" {
    return "Homepage"
}

# Route con HTML
route "/about" {
    return """
        

Chi Siamo

Benvenuti nella nostra azienda!

""" } # Route con template route "/contact" { return render("contact", { "email": "info@example.com", "phone": "+39 02 1234567" }) }

Parametri nelle Route

Le route possono catturare parametri dall'URL:

# Parametro semplice
route "/user/:id" {
    user = User.find(id)
    return render("user/profile", {"user": user})
}

# Parametri multipli
route "/blog/:year/:month/:slug" {
    post = Post.where(
        year=year,
        month=month,
        slug=slug
    ).first_or_404()
    
    return render("blog/post", {"post": post})
}

# Parametri opzionali
route "/products/:category?" {
    if category:
        products = Product.where(category=category).all()
    else:
        products = Product.all()
    
    return render("products/list", {"products": products})
}

Metodi HTTP

Specifica il metodo HTTP per ogni route:

# GET (default)
route.get "/articles" {
    return Article.all().to_json()
}

# POST
route.post "/articles" {
    article = Article.create(request.all())
    return {"id": article.id, "status": "created"}
}

# PUT/PATCH
route.put "/articles/:id" {
    article = Article.find(id)
    article.update(request.all())
    return {"status": "updated"}
}

# DELETE
route.delete "/articles/:id" {
    Article.find(id).delete()
    return {"status": "deleted"}
}

# Multiple metodi
route.match ["GET", "POST"] "/search" {
    if request.method == "POST":
        results = search(request.get("q"))
        return render("search/results", {"results": results})
    
    return render("search/form")
}

Gruppi di Route

Organizza route correlate in gruppi:

# Gruppo semplice
route.group "/api" {
    route "/users" {
        return User.all().to_json()
    }
    
    route "/posts" {
        return Post.all().to_json()
    }
}

# Gruppo con middleware
route.group "/admin" requires_auth requires_admin {
    route "/" {
        return render("admin/dashboard")
    }
    
    route "/users" uses AdminController.users
    route "/settings" uses AdminController.settings
}

# Gruppo con namespace
route.group "/api/v1" namespace="Api\V1" {
    route "/users" uses UserController.index
    route "/users/:id" uses UserController.show
}

Middleware

Applica middleware per controllare l'accesso o modificare le richieste:

# Middleware singolo
route "/dashboard" requires_auth {
    return render("dashboard", {
        "user": request.user
    })
}

# Middleware multipli
route "/admin" requires_auth requires_admin log_access {
    return render("admin/panel")
}

# Middleware con parametri
route "/api/data" throttle(60, 1) {  # 60 richieste al minuto
    return expensive_operation()
}

# Middleware inline
route "/profile" {
    if not request.user:
        return redirect("/login")
    
    return render("profile", {"user": request.user})
}

RESTful Routes

Crea rapidamente route RESTful complete:

# Resource completa
route.resource "/products" uses ProductController

# Genera automaticamente:
# GET    /products          -> index
# GET    /products/create   -> create
# POST   /products          -> store
# GET    /products/:id      -> show
# GET    /products/:id/edit -> edit
# PUT    /products/:id      -> update
# DELETE /products/:id      -> destroy

# Resource parziale
route.resource "/articles" uses ArticleController only=["index", "show"]

# API Resource (senza create/edit forms)
route.api_resource "/api/users" uses UserController

# Nested resources
route.resource "/posts" uses PostController {
    route.resource "/comments" uses CommentController
}
# Genera: /posts/:post_id/comments/:id

Named Routes

Assegna nomi alle route per riferimenti facili:

# Definisci route con nome
route "/user/profile" as="profile" {
    return render("user/profile")
}

route "/blog/:slug" as="blog.post" {
    # ...
}

# Usa i nomi nei template
{{ route('profile') }}                    # /user/profile
{{ route('blog.post', slug='mio-post') }} # /blog/mio-post

# Usa i nomi nel codice
return redirect(route('profile'))

# Con parametri
url = route('blog.post', {
    'slug': 'nuovo-articolo'
})

Route Constraints

Aggiungi vincoli ai parametri delle route:

# Vincoli regex
route "/user/:id" where id="\d+" {
    # id deve essere numerico
}

# Vincoli multipli
route "/blog/:year/:month" where year="\d{4}" month="\d{2}" {
    # year: 4 cifre, month: 2 cifre
}

# Vincoli predefiniti
route "/product/:slug" where slug="[a-z0-9-]+" {
    # Solo lettere minuscole, numeri e trattini
}

# Vincoli custom
route "/category/:name" where name=alpha {
    # Usa constraint predefinito 'alpha'
}

Subdomain Routing

Gestisci route per sottodomini:

# Sottodominio fisso
route.domain "api.example.com" {
    route "/users" {
        return User.all().to_json()
    }
}

# Sottodominio dinamico
route.subdomain ":tenant" {
    route "/" {
        company = Company.where(subdomain=tenant).first_or_404()
        return render("tenant/home", {"company": company})
    }
}

# Multipli sottodomini
route.domain ["api.example.com", "v2.api.example.com"] {
    route "/status" {
        return {"status": "ok", "version": "2.0"}
    }
}

Response Types

Flux gestisce automaticamente diversi tipi di risposta:

# JSON automatico
route "/api/user" {
    return {
        "name": "Mario",
        "email": "mario@example.com"
    }  # Content-Type: application/json
}

# Download file
route "/download/:file" {
    return download(f"storage/files/{file}")
}

# Redirect
route "/old-page" {
    return redirect("/new-page", 301)  # Permanent redirect
}

# Response custom
route "/custom" {
    return response("Custom content", 201, {
        "X-Custom-Header": "value"
    })
}

# Stream response
route "/stream" {
    def generate():
        for i in range(100):
            yield f"Chunk {i}\n"
    
    return stream(generate())
}

Error Handling

Gestisci errori nelle route:

# 404 personalizzato
route.error 404 {
    return render("errors/404", {
        "message": "Pagina non trovata"
    })
}

# Gestione eccezioni
route "/risky" {
    try:
        result = risky_operation()
        return {"data": result}
    except DatabaseError as e:
        log.error(e)
        return error(500, "Errore database")
}

# Abort con messaggio
route "/admin" {
    if not user.is_admin:
        abort(403, "Accesso negato")
    
    return render("admin/panel")
}

Route Cache e Performance

Ottimizza le performance delle route:

# Cache response
route "/expensive" cache(minutes=10) {
    data = expensive_computation()
    return render("data", {"data": data})
}

# Cache condizionale
route "/user/:id" {
    @cache_if(lambda: not request.user.is_admin, minutes=5)
    def get_user_data():
        return User.find(id).with_relations().to_dict()
    
    return get_user_data()
}

# ETag support
route "/api/resource/:id" {
    resource = Resource.find(id)
    etag = hash(resource.updated_at)
    
    if request.header("If-None-Match") == etag:
        return response("", 304)  # Not Modified
    
    return response(resource.to_json(), headers={
        "ETag": etag
    })
}

Route Testing

Testa le tue route facilmente:

# tests/test_routes.flux
def test_homepage():
    response = get("/")
    assert response.status == 200
    assert "Welcome" in response.text

def test_api_users():
    response = get("/api/users", headers={
        "Authorization": "Bearer token123"
    })
    assert response.status == 200
    assert response.json["users"] is not None

def test_create_post():
    response = post("/posts", {
        "title": "Test Post",
        "content": "Test content"
    })
    assert response.status == 201
    assert response.json["id"] > 0

Best Practices

🎯 Naming Convention

  • Usa kebab-case per gli URL: /user-profile
  • Nomi route in dot notation: user.profile
  • Verbi REST per le API: GET, POST, PUT, DELETE

πŸ”’ Sicurezza

  • Valida sempre i parametri delle route
  • Usa middleware per autenticazione
  • Limita rate per API pubbliche

πŸ“ Organizzazione

  • Raggruppa route correlate
  • Usa controller per logica complessa
  • Separa route API da web

Esempio Completo: Blog API

# app/routes.flux

# Route pubbliche
route.group "/api/v1" {
    # Lista post pubblici
    route.get "/posts" {
        posts = Post.published()
            .with("author", "tags")
            .paginate(request.get("per_page", 10))
        
        return posts.to_json()
    }
    
    # Singolo post
    route.get "/posts/:slug" where slug="[a-z0-9-]+" {
        post = Post.published()
            .where(slug=slug)
            .with("author", "comments.user")
            .first_or_404()
        
        # Incrementa visualizzazioni
        post.increment("views")
        
        return post.to_json()
    }
}

# Route autenticate
route.group "/api/v1" requires_auth {
    # Crea nuovo post
    route.post "/posts" {
        validated = validate(request.all(), {
            "title": "required|string|max:200",
            "content": "required|string",
            "tags": "array",
            "published": "boolean"
        })
        
        post = Post.create({
            **validated,
            "author_id": request.user.id,
            "slug": slugify(validated["title"])
        })
        
        return response(post.to_json(), 201)
    }
    
    # Aggiorna post
    route.put "/posts/:id" {
        post = Post.find_or_404(id)
        
        # Controlla permessi
        if post.author_id != request.user.id:
            abort(403, "Non autorizzato")
        
        post.update(request.only([
            "title", "content", "tags", "published"
        ]))
        
        return post.to_json()
    }
}

# Admin routes
route.group "/api/v1/admin" requires_auth requires_admin {
    # Tutti i post (inclusi bozze)
    route.get "/posts" {
        return Post.with_trashed()
            .with("author")
            .latest()
            .paginate()
            .to_json()
    }
    
    # Elimina qualsiasi post
    route.delete "/posts/:id" {
        Post.find(id).delete()
        return {"status": "deleted"}
    }
}
πŸš€ Prossimo passo: Ora che sai gestire le route, impara a creare Models per interagire con il database.