Request Lifecycle
Understanding how forge processes HTTP requests helps you build better applications and debug issues.
Overview​
When a request comes in, forge processes it through several stages:
HTTP Request
↓
Router (matches route)
↓
Middleware Stack
↓
Handler/ViewSet
↓
Authentication
↓
Permissions
↓
Business Logic
↓
Response
Step-by-step flow​
1. HTTP Request arrives​
A client sends an HTTP request to your forge application:
GET /api/posts HTTP/1.1
Host: localhost:8000
Authorization: Bearer token123
2. Router matches route​
The router (chi) matches the request to a registered route:
router.Get("/api/posts", listPostsHandler)
3. Middleware stack executes​
Middleware runs in registration order:
router.Use(middleware.RequestID)
router.Use(middleware.Logger)
router.Use(middleware.Recoverer)
router.Use(csrf.Protect)
router.Use(auth.RequireAuth)
Common middleware:
- Request ID - Adds unique ID to each request
- Logger - Logs request details
- Recoverer - Recovers from panics
- CSRF - Protects against CSRF attacks
- Auth - Authenticates users
4. Handler/ViewSet executes​
The matched handler or ViewSet processes the request:
func listPostsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
posts, err := Post.Objects.
Filter(Post.Fields.Published.Equals(true)).
All(ctx)
// Return JSON response
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(posts)
}
5. Authentication (if required)​
If the route requires authentication:
user := auth.GetUser(r.Context())
// Or use middleware
router.Use(auth.RequireAuth)
6. Permissions (if required)​
Check if user has required permissions:
if !users.HasPermission(user, "posts.view_post") {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
7. Business logic​
Execute your application logic:
posts, err := Post.Objects.
Filter(Post.Fields.AuthorID.Equals(user.ID)).
All(ctx)
for _, post := range posts {
post.ViewCount++
Post.Objects.Update(ctx, post)
}
8. Response​
Return response to client:
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(posts)
// Or return HTML
templates.ExecuteTemplate(w, "posts.html", posts)
Example: Complete flow​
Here's a complete example:
router.Get("/api/posts", listPostsHandler)
router.Use(middleware.Logger)
router.Use(auth.RequireAuth)
func listPostsHandler(w http.ResponseWriter, r *http.Request) {
user := auth.GetUser(r.Context())
if !users.HasPermission(user, "posts.view_post") {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
ctx := r.Context()
posts, err := Post.Objects.
Filter(Post.Fields.Published.Equals(true)).
All(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(posts)
}
ViewSet flow​
For REST API ViewSets, the flow is more structured:
Request
↓
ViewSet Handler
↓
Authentication
↓
Permissions
↓
Throttling
↓
Content Negotiation
↓
Parse Request
↓
ViewSet Action (list, create, etc.)
↓
Serialize
↓
Render Response
Error handling​
Errors can occur at any stage:
- Routing errors - 404 Not Found
- Authentication errors - 401 Unauthorized
- Permission errors - 403 Forbidden
- Validation errors - 400 Bad Request
- Server errors - 500 Internal Server Error
func listPostsHandler(w http.ResponseWriter, r *http.Request) {
posts, err := Post.Objects.All(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(posts)
}
Context propagation​
The request context carries important information:
ctx := r.Context()
posts, err := Post.Objects.All(ctx)
Performance considerations​
Middleware order matters​
Place expensive middleware later in the chain:
router.Use(middleware.RequestID)
router.Use(middleware.Logger)
// Expensive operations go last
router.Use(auth.RequireAuth)
router.Use(rateLimit.Middleware)
Database connection pooling​
forge uses connection pooling automatically:
database:
max_connections: 25
max_idle_connections: 5
Query optimization​
Use SelectRelated and PrefetchRelated to avoid N+1 queries:
// Single query with JOIN
posts, err := Post.Objects.
SelectRelated("author").
All(ctx)
// N+1 queries (avoid this)
posts, err := Post.Objects.All(ctx)
for _, post := range posts {
author := post.Author
}
Next steps​
- Middleware - Learn about middleware
- Authentication - Understand authentication
- Error Handling - Handle errors properly