Overview
FastrAPI provides multiple response types for returning different content formats. All response classes are implemented in Rust for maximum performance.
JSONResponse
The default response type for returning JSON data:
from fastrapi import FastrAPI
from fastrapi.responses import JSONResponse
app = FastrAPI()
@app.get ( "/users" )
def get_users () -> JSONResponse:
return JSONResponse({
"users" : [
{ "id" : 1 , "name" : "Alice" },
{ "id" : 2 , "name" : "Bob" }
]
})
# Equivalent shorthand (JSONResponse is default)
@app.get ( "/items" )
def get_items ():
return { "items" : [ "item1" , "item2" ]}
Constructor
JSONResponse(content, status_code = 200 )
Parameters:
content: Any JSON-serializable Python object (dict, list, etc.)
status_code: HTTP status code (default: 200)
Implementation
JSONResponse is a Rust struct that serializes Python objects to JSON:
// src/responses.rs
#[pyclass(name = "JSONResponse" )]
#[derive( Clone )]
pub struct PyJSONResponse {
#[pyo3(get)]
pub content : Py < PyAny >,
#[pyo3(get)]
pub status_code : u16 ,
}
#[pymethods]
impl PyJSONResponse {
#[new]
#[pyo3(signature = (content, status_code = 200))]
fn new ( content : Py < PyAny >, status_code : u16 ) -> Self {
Self { content , status_code }
}
}
The content is converted to JSON using Rust’s serde_json:
// src/py_handlers.rs
fn convert_json_response ( py : Python , result : & Bound < PyAny >) -> Response {
if let Ok ( resp ) = result . extract :: < PyRef <' _ , PyJSONResponse >>() {
let status_code = StatusCode :: from_u16 ( resp . status_code)
. unwrap_or ( StatusCode :: OK );
let json = py_any_to_json ( py , & resp . content . bind ( py ));
( status_code , Json ( json )) . into_response ()
}
}
Status codes
Customize the HTTP status code:
from fastrapi.responses import JSONResponse
@app.post ( "/users" )
def create_user ( user ):
# 201 Created
return JSONResponse(
{ "id" : 1 , "name" : user.name},
status_code = 201
)
@app.get ( "/not-found" )
def not_found ():
# 404 Not Found
return JSONResponse(
{ "error" : "Resource not found" },
status_code = 404
)
HTMLResponse
Return HTML content:
from fastrapi.responses import HTMLResponse
@app.get ( "/hello" )
def hello_html () -> HTMLResponse:
return HTMLResponse( """
<!DOCTYPE html>
<html>
<head><title>FastrAPI</title></head>
<body>
<h1>Hello from FastrAPI!</h1>
<p>This is HTML content.</p>
</body>
</html>
""" )
Constructor
HTMLResponse(content, status_code = 200 )
Parameters:
content: HTML string
status_code: HTTP status code (default: 200)
Implementation
#[pyclass(name = "HTMLResponse" )]
#[derive( Clone )]
pub struct PyHTMLResponse {
#[pyo3(get)]
pub content : String ,
#[pyo3(get)]
pub status_code : u16 ,
}
HTMLResponse sets the Content-Type header to text/html; charset=utf-8:
fn convert_html_response ( _py : Python , result : & Bound < PyAny >) -> Response {
if let Ok ( resp ) = result . extract :: < PyRef <' _ , PyHTMLResponse >>() {
let status_code = StatusCode :: from_u16 ( resp . status_code)
. unwrap_or ( StatusCode :: OK );
( status_code , Html ( resp . content . clone ())) . into_response ()
}
}
Dynamic HTML
Generate HTML dynamically:
@app.get ( "/user/ {name} " )
def user_page ( name : str ) -> HTMLResponse:
html = f """
<!DOCTYPE html>
<html>
<head><title> { name } 's Profile</title></head>
<body>
<h1>Welcome, { name } !</h1>
<p>This is your profile page.</p>
</body>
</html>
"""
return HTMLResponse(html)
PlainTextResponse
Return plain text content:
from fastrapi.responses import PlainTextResponse
@app.get ( "/text" )
def plain_text () -> PlainTextResponse:
return PlainTextResponse( "This is plain text content." )
@app.get ( "/log" )
def get_log () -> PlainTextResponse:
return PlainTextResponse(
"2024-01-01 12:00:00 INFO Server started \n "
"2024-01-01 12:00:01 INFO Request received \n "
"2024-01-01 12:00:02 INFO Response sent"
)
Constructor
PlainTextResponse(content, status_code = 200 )
Parameters:
content: Plain text string
status_code: HTTP status code (default: 200)
Implementation
#[pyclass(name = "PlainTextResponse" )]
#[derive( Clone )]
pub struct PyPlainTextResponse {
#[pyo3(get)]
pub content : String ,
#[pyo3(get)]
pub status_code : u16 ,
}
Sets Content-Type to text/plain; charset=utf-8:
fn convert_text_response ( _py : Python , result : & Bound < PyAny >) -> Response {
if let Ok ( resp ) = result . extract :: < PyRef <' _ , PyPlainTextResponse >>() {
let status_code = StatusCode :: from_u16 ( resp . status_code)
. unwrap_or ( StatusCode :: OK );
(
status_code ,
[( header :: CONTENT_TYPE , "text/plain; charset=utf-8" )],
resp . content . clone (),
)
. into_response ()
}
}
RedirectResponse
Redirect to another URL:
from fastrapi.responses import RedirectResponse
@app.get ( "/old-path" )
def old_endpoint () -> RedirectResponse:
# 307 Temporary Redirect (default)
return RedirectResponse( "/new-path" )
@app.get ( "/moved" )
def permanent_redirect () -> RedirectResponse:
# 301 Permanent Redirect
return RedirectResponse(
"https://example.com/new-location" ,
status_code = 301
)
Constructor
RedirectResponse(url, status_code = 307 )
Parameters:
url: Target URL (can be relative or absolute)
status_code: HTTP redirect status code (default: 307)
Common redirect status codes
Code Description Use case 301 Permanent Redirect Resource moved permanently 302 Found Temporary redirect (old) 303 See Other Redirect after POST 307 Temporary Redirect Temporary redirect (default) 308 Permanent Redirect Permanent redirect with method
Implementation
#[pyclass(name = "RedirectResponse" )]
#[derive( Clone )]
pub struct PyRedirectResponse {
#[pyo3(get)]
pub url : String ,
#[pyo3(get)]
pub status_code : u16 ,
}
Redirects use Axum’s Redirect helper:
fn convert_redirect_response ( _py : Python , result : & Bound < PyAny >) -> Response {
if let Ok ( resp ) = result . extract :: < PyRef <' _ , PyRedirectResponse >>() {
if resp . status_code == 301 {
Redirect :: permanent ( & resp . url) . into_response ()
} else {
Redirect :: temporary ( & resp . url) . into_response ()
}
}
}
Auto response detection
If you don’t specify a response type, FastrAPI automatically converts your return value:
@app.get ( "/auto" )
def auto_response ():
# Automatically converted to JSONResponse
return { "message" : "Auto-detected" }
@app.get ( "/null" )
def null_response ():
# Returns 204 No Content
return None
The auto-detection logic:
fn convert_auto_response ( py : Python , result : & Bound < PyAny >) -> Response {
if result . is_none () {
return StatusCode :: NO_CONTENT . into_response ();
}
// Convert to JSON
let json = py_any_to_json ( py , result );
( StatusCode :: OK , Json ( json )) . into_response ()
}
Response type annotation
Use type hints to specify the response type:
from fastrapi.responses import HTMLResponse, JSONResponse, PlainTextResponse
@app.get ( "/html" )
def html_page () -> HTMLResponse:
return HTMLResponse( "<h1>Hello</h1>" )
@app.get ( "/json" )
def json_data () -> JSONResponse:
return JSONResponse({ "data" : "value" })
@app.get ( "/text" )
def text_data () -> PlainTextResponse:
return PlainTextResponse( "Hello World" )
FastrAPI parses return type annotations at decorator time to optimize response handling:
// src/pydantic.rs
let response_type = if let Ok ( return_annotation ) = func . getattr ( "__annotations__" ) {
// Check return type annotation
if annotation_str . contains ( "HTMLResponse" ) {
ResponseType :: Html
} else if annotation_str . contains ( "JSONResponse" ) {
ResponseType :: Json
} else if annotation_str . contains ( "PlainTextResponse" ) {
ResponseType :: PlainText
} else if annotation_str . contains ( "RedirectResponse" ) {
ResponseType :: Redirect
} else {
ResponseType :: Auto
}
} else {
ResponseType :: Auto
};
Complete example
from fastrapi import FastrAPI
from fastrapi.responses import (
HTMLResponse,
JSONResponse,
PlainTextResponse,
RedirectResponse
)
app = FastrAPI()
@app.get ( "/" )
def root () -> HTMLResponse:
return HTMLResponse( """
<html>
<body>
<h1>Welcome to FastrAPI</h1>
<ul>
<li><a href="/api/data">JSON API</a></li>
<li><a href="/text">Plain Text</a></li>
<li><a href="/redirect">Redirect</a></li>
</ul>
</body>
</html>
""" )
@app.get ( "/api/data" )
def api_data () -> JSONResponse:
return JSONResponse({
"users" : [
{ "id" : 1 , "name" : "Alice" },
{ "id" : 2 , "name" : "Bob" }
],
"total" : 2
})
@app.get ( "/text" )
def text_content () -> PlainTextResponse:
return PlainTextResponse(
"This is plain text content. \n "
"It can span multiple lines."
)
@app.get ( "/redirect" )
def redirect_home () -> RedirectResponse:
return RedirectResponse( "/" )
@app.post ( "/users" )
def create_user ( user ) -> JSONResponse:
return JSONResponse(
{ "id" : 1 , "name" : user.get( "name" )},
status_code = 201
)
@app.get ( "/error" )
def error_response () -> JSONResponse:
return JSONResponse(
{ "error" : "Something went wrong" },
status_code = 500
)
if __name__ == "__main__" :
app.serve( "127.0.0.1" , 8080 )
All response types are implemented in Rust and converted to HTTP responses without leaving Rust-land, making them significantly faster than pure Python implementations.
Benchmarks:
Response Type FastAPI FastrAPI Improvement JSONResponse 937 req/s 31,360 req/s 33x HTMLResponse 1,024 req/s 33,200 req/s 32x PlainTextResponse 1,150 req/s 35,800 req/s 31x RedirectResponse 1,089 req/s 34,500 req/s 32x
Next steps
Dependency injection Learn about dependency injection
Architecture Understand FastrAPI’s internal architecture