Responses
Framefox controllers provide several methods to return different types of HTTP responses. All response types are built on top of FastAPI’s response classes.
HTMLResponse with render()
Render HTML templates and return them as HTTP responses.
Basic Usage
from framefox.core.controller.abstract_controller import AbstractController
from framefox.core.routing.decorator.route import Route
class PageController ( AbstractController ):
@Route ( path = "/about" , name = "about" , methods = [ "GET" ])
async def about ( self ):
return self .render( "about.html" )
Passing Context Data
Pass data to templates using the context parameter:
@Route ( path = "/users/ {user_id} " , name = "user_profile" , methods = [ "GET" ])
async def show_profile ( self , user_id : int ):
user = {
"id" : user_id,
"name" : "John Doe" ,
"email" : "[email protected] " ,
"joined" : "2024-01-15"
}
return self .render( "user/profile.html" , {
"user" : user,
"page_title" : "User Profile"
})
Template Path Resolution
Template paths are relative to your configured templates directory:
# Renders: templates/products/list.html
return self .render( "products/list.html" , { "products" : products})
# Renders: templates/admin/dashboard.html
return self .render( "admin/dashboard.html" , { "stats" : stats})
Method Signature
Path to the template file to render, relative to the templates directory
Dictionary of variables to pass to the template. Available in the template as {{ variable_name }}
Returns: HTMLResponse with status code 200
Raises: Exception if the template file is not found or contains errors
JSONResponse with json()
Return data as JSON, typically for API endpoints.
Basic Usage
class ApiController ( AbstractController ):
@Route ( path = "/api/status" , name = "api_status" , methods = [ "GET" ])
async def status ( self ):
return self .json({
"status" : "ok" ,
"version" : "1.0.0" ,
"timestamp" : "2024-01-15T10:30:00Z"
})
Custom Status Codes
Set custom HTTP status codes for different scenarios:
@Route ( path = "/api/users" , name = "api_user_create" , methods = [ "POST" ])
async def create_user ( self , request : Request):
data = await request.json()
# Create user logic here
user = {
"id" : 123 ,
"name" : data[ "name" ],
"email" : data[ "email" ]
}
# Return 201 Created
return self .json(user, status = 201 )
Error Responses
Return error messages with appropriate status codes:
@Route ( path = "/api/users/ {user_id} " , name = "api_user_detail" , methods = [ "GET" ])
async def get_user ( self , user_id : int , user_service : UserService):
user = user_service.find(user_id)
if not user:
return self .json(
{ "error" : "User not found" , "user_id" : user_id},
status = 404
)
return self .json({
"id" : user.id,
"name" : user.name,
"email" : user.email
})
Common Status Codes
200 OK - Successful GET request
201 Created - Successful POST request that created a resource
204 No Content - Successful request with no response body
400 Bad Request - Invalid request data
401 Unauthorized - Authentication required
403 Forbidden - Insufficient permissions
404 Not Found - Resource doesn’t exist
422 Unprocessable Entity - Validation failed
500 Internal Server Error - Server error
Method Signature
The data to serialize as JSON. Must be JSON-serializable (dict, list, str, int, float, bool, None)
HTTP status code for the response
Returns: JSONResponse with the specified status code
Nested Data Structures
@Route ( path = "/api/dashboard" , name = "api_dashboard" , methods = [ "GET" ])
async def dashboard ( self ):
return self .json({
"user" : {
"id" : 1 ,
"name" : "John Doe" ,
"roles" : [ "admin" , "editor" ]
},
"stats" : {
"posts" : 42 ,
"comments" : 158 ,
"likes" : 523
},
"recent_activity" : [
{ "type" : "post" , "title" : "New article" , "date" : "2024-01-15" },
{ "type" : "comment" , "content" : "Great post!" , "date" : "2024-01-14" }
]
})
RedirectResponse with redirect()
Redirect users to different URLs.
Basic Usage
class AuthController ( AbstractController ):
@Route ( path = "/logout" , name = "logout" , methods = [ "POST" ])
async def logout ( self ):
# Clear session/cookies
return self .redirect( "/login" )
@Route ( path = "/posts/create" , name = "post_create" , methods = [ "GET" , "POST" ])
async def create_post ( self , request : Request):
if request.method == "GET" :
return self .render( "posts/create.html" )
# Handle POST - create the post
# ... save post logic ...
self .flash( "success" , "Post created successfully!" )
return self .redirect( "/posts" )
Redirect with Different Status Codes
# Temporary redirect (302) - default
@Route ( path = "/temp-page" , name = "temp_page" , methods = [ "GET" ])
async def temp_page ( self ):
return self .redirect( "/new-page" ) # 302 Found
# Permanent redirect (301)
@Route ( path = "/old-url" , name = "old_url" , methods = [ "GET" ])
async def old_url ( self ):
return self .redirect( "/new-url" , code = 301 ) # 301 Moved Permanently
# See Other (303) - after POST request
@Route ( path = "/form-submit" , name = "form_submit" , methods = [ "POST" ])
async def form_submit ( self ):
# Process form
return self .redirect( "/success" , code = 303 ) # 303 See Other
# Temporary redirect (307) - preserves request method
@Route ( path = "/api-v1/users" , name = "api_v1_users" , methods = [ "POST" ])
async def api_v1_users ( self ):
return self .redirect( "/api-v2/users" , code = 307 ) # 307 Temporary Redirect
Redirect to Named Routes
Combine generate_url() with redirect() for type-safe redirects:
@Route ( path = "/users/ {user_id} /delete" , name = "user_delete" , methods = [ "POST" ])
async def delete_user ( self , user_id : int ):
# Delete user logic
self .flash( "success" , f "User { user_id } deleted" )
# Redirect to the user list using route name
return self .redirect( self .generate_url( "user_list" ))
Redirect with Query Parameters
@Route ( path = "/search" , name = "search" , methods = [ "POST" ])
async def search_form ( self , request : Request):
form_data = await request.form()
query = form_data.get( "query" )
# Redirect to GET endpoint with query parameter
return self .redirect( f "/search/results?q= { query } " )
Method Signature
The URL to redirect to. Can be a relative path (e.g., /users) or absolute URL (e.g., https://example.com)
HTTP status code for the redirect. Common values:
301 - Moved Permanently
302 - Found (temporary redirect)
303 - See Other (typically after POST)
307 - Temporary Redirect (preserves method)
308 - Permanent Redirect (preserves method)
Returns: RedirectResponse with the specified status code
Custom Responses
For advanced use cases, you can return custom FastAPI responses directly:
Plain Text Response
from fastapi.responses import PlainTextResponse
@Route ( path = "/robots.txt" , name = "robots" , methods = [ "GET" ])
async def robots ( self ):
content = """
User-agent: *
Disallow: /admin/
Allow: /
"""
return PlainTextResponse( content = content)
File Response
from fastapi.responses import FileResponse
@Route ( path = "/download/ {file_id} " , name = "download_file" , methods = [ "GET" ])
async def download ( self , file_id : int ):
file_path = f "/var/files/ { file_id } .pdf"
return FileResponse(
path = file_path,
filename = "document.pdf" ,
media_type = "application/pdf"
)
Streaming Response
from fastapi.responses import StreamingResponse
import io
@Route ( path = "/export/csv" , name = "export_csv" , methods = [ "GET" ])
async def export_csv ( self ):
def generate_csv ():
yield "Name,Email,Age \n "
yield "John Doe,[email protected] ,30 \n "
yield "Jane Smith,[email protected] ,25 \n "
return StreamingResponse(
generate_csv(),
media_type = "text/csv" ,
headers = { "Content-Disposition" : "attachment; filename=users.csv" }
)
from fastapi.responses import Response
@Route ( path = "/api/data" , name = "api_data" , methods = [ "GET" ])
async def api_data ( self ):
response = self .json({ "data" : "value" })
response.headers[ "X-Custom-Header" ] = "CustomValue"
response.headers[ "Cache-Control" ] = "max-age=3600"
return response
Response Patterns
API Response Wrapper
Create consistent API responses:
class ApiController ( AbstractController ):
def api_response ( self , data = None , message = None , status = 200 ):
return self .json({
"success" : 200 <= status < 300 ,
"message" : message,
"data" : data
}, status = status)
@Route ( path = "/api/users/ {user_id} " , name = "api_user_get" , methods = [ "GET" ])
async def get_user ( self , user_id : int ):
user = { "id" : user_id, "name" : "John" }
return self .api_response(
data = user,
message = "User retrieved successfully"
)
Conditional Responses
Return different response types based on request headers:
@Route ( path = "/users/ {user_id} " , name = "user_detail" , methods = [ "GET" ])
async def user_detail ( self , user_id : int , request : Request):
user = { "id" : user_id, "name" : "John Doe" }
# Check if client wants JSON
accept = request.headers.get( "Accept" , "" )
if "application/json" in accept:
return self .json(user)
# Default to HTML
return self .render( "user/detail.html" , { "user" : user})
Best Practices
Use appropriate status codes
Always return the correct HTTP status code. Use 201 for created resources, 404 for not found, etc.
Redirect after POST requests
Follow the Post-Redirect-Get (PRG) pattern to prevent duplicate form submissions.
Use flash messages with redirects
When redirecting after an action, add a flash message to inform the user of the result.
Use Pydantic models to validate JSON request bodies before processing.
Return appropriate error responses with helpful messages instead of letting exceptions propagate.
Next Steps
Forms Learn about form handling and validation
Controllers Overview Back to controller basics