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.