Applications
Use the JinjaMiddleware middleware and TemplateRouter ASGI app to add template support to Starlette applications.
Project Structure
A typical Starlette-Templates application is organized as a Python package:
myapp/
├── __init__.py
├── app.py # Main application file
├── templates/ # Template files
│ ├── base.html # Base layout
│ ├── index.html # Homepage
│ ├── about.html # About page
│ ├── 404.html # Custom 404 page
│ ├── 500.html # Custom 500 page
│ ├── partials/ # Reusable fragments
│ │ ├── navigation.html
│ │ └── footer.html
│ └── components/ # Component templates
│ ├── alert.html
│ └── card.html
└── static/ # Static files
├── css/
├── js/
└── images/
Your app.py would typically look like:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.routing import Mount, Route
from starlette.responses import JSONResponse
from jinja2 import PackageLoader
from importlib.resources import files
from starlette_templates.routing import TemplateRouter
from starlette_templates.staticfiles import StaticFiles
from starlette_templates.middleware import JinjaMiddleware
PKG_DIR = files("myapp")
async def api(request):
return JSONResponse({"message": "API Root"})
app = Starlette(
routes=[
# Custom routes first
Mount("/api", api, name="api"),
# Static files
Mount("/static", StaticFiles(directories=[PKG_DIR / "static"]), name="static"),
# Template files for remaining routes
Mount("/", TemplateRouter()),
],
middleware=[
Middleware(
JinjaMiddleware,
template_loaders=[PackageLoader("myapp", "templates")],
)
]
)
Basic Setup
A minimal Starlette Templates application requires two components:
- JinjaMiddleware - Middleware that configures the Jinja2 environment, making it available on request state
- TemplateRouter - An ASGI app that serves HTML templates
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.routing import Mount
from jinja2 import PackageLoader
from starlette_templates.routing import TemplateRouter
from starlette_templates.middleware import JinjaMiddleware
app = Starlette(
routes=[
Mount("/", TemplateRouter()),
],
middleware=[
Middleware(
JinjaMiddleware,
template_loaders=[PackageLoader("mypackage", "templates")],
)
]
)
Static Files
Starlette Templates provides enhanced static file handling with gzip compression and multi-directory support using StaticFiles.
from starlette.routing import Mount
from starlette_templates.staticfiles import StaticFiles
app = Starlette(
routes=[
Mount("/static", StaticFiles(directories=["static"]), name="static"),
Mount("/", TemplateRouter()),
]
)
For detailed information about static file handling, including pre-compressed files, multi-directory support, and HTTP caching, see the Static Files guide.
Custom Routes
Override the default template routing by defining custom routes. Routes defined before the TemplateRouter mount take precedence:
from starlette.routing import Route
from starlette.requests import Request
from starlette_templates.responses import TemplateResponse
async def homepage(request: Request) -> TemplateResponse:
return TemplateResponse("home.html", context={"title": "Welcome"})
app = Starlette(
routes=[
# Custom route for homepage
Route("/", homepage, name="home"),
# Template routing for all other routes
Mount("/", TemplateRouter())
],
middleware=[
Middleware(
JinjaMiddleware,
template_loaders=[PackageLoader("myapp", "templates")]
)
]
)
Middleware Configuration
JinjaMiddleware
JinjaMiddleware configures and injects the Jinja2 environment into request state, making it available at request.state.jinja_env.
from jinja2 import PackageLoader, FileSystemLoader
from starlette_templates.middleware import JinjaMiddleware
app = Starlette(
middleware=[
Middleware(
JinjaMiddleware,
template_loaders=[
PackageLoader("myapp", "templates"),
FileSystemLoader("custom/templates"),
],
include_default_loader=True, # Include built-in templates
)
]
)
The middleware provides:
- Automatic template loader configuration
- Built-in Jinja2 globals:
url_for,url,absurl,jsonify - Request state injection for all templates
Registering Components
Register ComponentModel classes to make them available in all templates:
from starlette_templates.components.base import ComponentModel
class Alert(ComponentModel):
template: str = "components/alert.html"
message: str
variant: str = "info"
app = Starlette(
middleware=[
Middleware(
JinjaMiddleware,
template_loaders=[PackageLoader("myapp", "templates")],
extra_components=[Alert], # Register component
)
]
)
Then use directly in templates:
{{ Alert(message="System error", variant="danger") }}
Context Processors
Add custom context variables to all templates using context processors:
from starlette.requests import Request
async def add_user_context(request: Request) -> dict:
return {"user": await get_current_user(request)}
async def add_site_context(request: Request) -> dict:
return {
"site_name": "My Site",
"year": datetime.now().year,
}
templates = TemplateRouter(
context_processors=[add_user_context, add_site_context]
)
Context processors are async functions that receive the Request object and return a dictionary of context variables.
Route-Specific Context Processors
Context processors can also be applied to specific routes using [Route][starlette.routing.Route] objects with path patterns:
from starlette.requests import Request
from starlette.routing import Route
async def add_product_context(request: Request) -> dict:
"""Context processor that only runs for product pages."""
product_id = request.path_params.get("product_id")
return {"product": await get_product(product_id)}
async def add_user_context(request: Request) -> dict:
"""Global context processor that runs for all templates."""
return {"user": await get_current_user(request)}
templates = TemplateRouter(
context_processors=[
add_user_context, # Runs for all templates
# Only runs when path matches /products/{product_id}
Route("/products/{product_id}", add_product_context),
]
)
Route-specific context processors only execute when the URL path matches the route pattern, allowing you to add context data conditionally based on the request path. Path parameters from the route (like {product_id}) are available via request.path_params.
Error Handling
Starlette Templates provides error handling with custom exceptions, error pages, and JSON:API error responses.
Create custom error pages by adding templates like 404.html, 500.html, or error.html to your template directory. The framework automatically serves these pages for browser requests and returns JSON:API compliant responses for API requests.
For detailed information about error handling, custom exceptions, error pages, and JSON:API responses, see the Error Handling guide.
HTTP Caching
Configure HTTP caching with ETag and Last-Modified headers on TemplateRouter:
templates = TemplateRouter(
cache_max_age=3600, # Cache for 1 hour
gzip_min_size=1024, # Compress files > 1KB
)
TemplateRouter automatically:
- Generates ETag headers based on file modification time
- Sets Last-Modified headers
- Returns 304 Not Modified responses when appropriate
- Compresses responses over the threshold with gzip