Skip to content

Templating

Nexios provides a powerful templating system built on top of Jinja2, offering features like template inheritance, context management, custom filters, and more.

Quick Start

python
from nexios import NexiosApp
from nexios.templating import render,TemplateEngine

app = NexiosApp()
engine = TemplateEngine()
engine.setup_environment()

@app.get("/")
async def home(request, response):
    return await render("home.html", {"title": "Welcome"}, request=request)

Tip

Without setting up the templating engine , the render function throws a Notimpemented error

Customizing Default Configuration

There are several ways to customize the templating system:

1. Using setup_environment

The simplest way is to set template options in your app configuration:

py
from nexios.templating import  TemplateConfig

template_config = TemplateConfig(
    template_dir = "templates"
)

engine.setup_environment(template_config)

2. Using App Config

You can also nexios app config optionally

python
from nexios import NexiosApp,MakeConfig
from nexios.templating import  TemplateConfig

config = MakeConfig(
    {
        "templating" : TemplateConfig(
            template_dir = "templates"
        )
    }
)


app = NexiosApp(config = config)

3. Runtime Configuration Updates

You can update configuration at runtime:

python
from nexios.templating import TemplateEngine
from pathlib import Path

engine = TemplateEngine()
engine.setup_environment()


engine.env.filters["custom"] = my_custom_filter

# Add new globals
engine.env.globals.update({
    "api_version": "2.0",
    "debug": True
})

engine.config.template_dir = Path("new_templates")
engine._setup_environment()

Configuration Options

The TemplateConfig class supports the following options:

OptionTypeDefaultDescription
template_dirstr/Path"templates"Template directory path
cache_sizeint100Maximum templates to cache
auto_reloadboolTrueReload changed templates
encodingstr"utf-8"Template file encoding
enable_asyncboolTrueEnable async rendering
trim_blocksboolTrueStrip first newline after block
lstrip_blocksboolTrueStrip leading spaces and tabs
custom_filtersDict[str, callable]{}Custom template filters
custom_globalsDict[str, Any]{}Global template variables

Template Inheritance

Base template (base.html):

html
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <nav>{% block nav %}{% endblock %}</nav>
    <main>{% block content %}{% endblock %}</main>
    <footer>{% block footer %}{% endblock %}</footer>
</body>
</html>

Child template:

html
{% extends "base.html" %}

{% block title %}Welcome{% endblock %}

{% block content %}
    <h1>{{ title }}</h1>
    {{ content }}
{% endblock %}

Context Middleware

Add global and request-specific context to your templates:

python
from nexios.templating.middleware import template_context

async def user_context(request):
    return {
        "user": await get_current_user(request),
        "messages": await get_flash_messages(request)
    }

app.add_middleware(template_context(
    default_context={
        "version": "1.0.0",
        "nav_links": [...]
    },
    context_processor=user_context
))

Utility Functions

The templating system includes several utility functions:

python
from nexios.templating.utils import (
    truncate,
    format_datetime,
    static_hash,
    merge_dicts
)

# Truncate text
{{ long_text|truncate(100) }}

# Format dates
{{ date|format_datetime("%Y-%m-%d") }}

# Cache busting for static files
{{ static_url('style.css') }}?v={{ static_hash('static/style.css') }}

Best Practices

  1. Template Organization

    • Keep templates in a dedicated directory
    • Use meaningful names and subdirectories
    • Follow a consistent naming convention
  2. Context Management

    • Use middleware for global context
    • Keep context processors focused and lightweight
    • Cache expensive context operations
  3. Performance

    • Enable template caching in production
    • Use async rendering for I/O operations
    • Minimize template complexity
  4. Security

    • HTML escaping is enabled by default
    • Use |safe filter carefully
    • Validate user input before rendering

API Reference

Render Function

python
async def render(
    template_name: str,
    context: Dict[str, Any] = None,
    status_code: int = 200,
    headers: Dict[str, str] = None,
    request: Request = None,
    **kwargs
) -> Response

Template Context Middleware

python
def template_context(
    default_context: Optional[Dict[str, Any]] = None,
    context_processor: Optional[
        Callable[[Request], Awaitable[Dict[str, Any]]]
    ] = None
) -> TemplateContextMiddleware

Utility Functions

python
def truncate(text: str, length: int = 100, suffix: str = "...") -> str
def format_datetime(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str
def static_hash(filepath: str) -> str
def merge_dicts(*dicts: Dict[str, Any]) -> Dict[str, Any]

Advanced Templating

This guide covers advanced features and patterns for the Nexios templating system.

Custom Filters

Create and register custom template filters:

python
from nexios.templating import TemplateConfig

def markdown_to_html(text: str) -> str:
    import markdown
    return markdown.markdown(text)

def currency(value: float, symbol: str = "$") -> str:
    return f"{symbol}{value:,.2f}"

config = TemplateConfig(
    custom_filters={
        "markdown": markdown_to_html,
        "currency": currency
    }
)

Usage in templates:

html
{{ post.content|markdown }}
{{ product.price|currency("€") }}

Macros

Create reusable template components:

html
{# macros/forms.html #}
{% macro input(name, value='', type='text', label='') %}
    <div class="form-group">
        {% if label %}
        <label for="{{ name }}">{{ label }}</label>
        {% endif %}
        <input type="{{ type }}" name="{{ name }}" 
               value="{{ value }}" id="{{ name }}">
    </div>
{% endmacro %}

{# Usage in templates #}
{% from "macros/forms.html" import input %}
<form method="post">
    {{ input('username', label='Username') }}
    {{ input('password', type='password', label='Password') }}
</form>

Async Template Functions

Create async template functions for database queries or API calls:

python
from nexios.templating import TemplateConfig
from functools import partial

async def get_user_posts(user_id: int):
    # Async database query
    return await db.query("SELECT * FROM posts WHERE user_id = $1", user_id)

config = TemplateConfig(
    custom_globals={
        "get_posts": get_user_posts
    }
)

Usage in templates:

html
{% set posts = await get_posts(user.id) %}
{% for post in posts %}
    <article>{{ post.title }}</article>
{% endfor %}

Context Processors

Advanced context processor patterns:

python
from typing import Dict, Any
from nexios.templating.middleware import template_context
from nexios.cache import cached

class ContextBuilder:
    def __init__(self):
        self.processors = []
    
    def add(self, processor):
        self.processors.append(processor)
        return self
    
    async def build(self, request) -> Dict[str, Any]:
        context = {}
        for proc in self.processors:
            context.update(await proc(request))
        return context

@cached(ttl=300)  # Cache for 5 minutes
async def get_site_stats(request):
    return {
        "total_users": await db.count("users"),
        "total_posts": await db.count("posts")
    }

async def get_user_data(request):
    if user := await get_current_user(request):
        return {
            "user": user,
            "notifications": await get_user_notifications(user.id)
        }
    return {}

# Combine processors
context_builder = (
    ContextBuilder()
    .add(get_site_stats)
    .add(get_user_data)
)

app.add_middleware(template_context(
    context_processor=context_builder.build
))

Template Caching

Implement template fragment caching:

python
from nexios.cache import Cache
from nexios.templating import TemplateConfig

cache = Cache()

async def cached_fragment(key: str, ttl: int = 300):
    """Template fragment cache."""
    if content := await cache.get(key):
        return content
    return None

config = TemplateConfig(
    custom_globals={
        "cached_fragment": cached_fragment,
        "cache": cache
    }
)

Usage in templates:

html
{% set cache_key = 'sidebar_' + user.id %}
{% set cached = await cached_fragment(cache_key) %}
{% if cached %}
    {{ cached }}
{% else %}
    {% set content %}
        {# Expensive sidebar rendering #}
        <aside>...</aside>
    {% endset %}
    {{ cache.set(cache_key, content, ttl=300) }}
    {{ content }}
{% endif %}

Error Handling

Custom error templates and handling:

python
from nexios.templating import render
from nexios.exceptions import TemplateError

@app.exception_handler(404)
async def not_found(request, exc):
    return await render(
        "errors/404.html",
        {"path": request.url.path},
        status_code=404
    )

@app.exception_handler(TemplateError)
async def template_error(request, exc):
    return await render(
        "errors/template.html",
        {
            "error": str(exc),
            "template": exc.template_name,
            "lineno": exc.lineno
        },
        status_code=500
    )

Testing Templates

Write tests for your templates:

python
import pytest
from nexios.templating import TemplateEngine, TemplateConfig
from nexios.testing import TestClient

@pytest.fixture
def template_engine():
    config = TemplateConfig(template_dir="tests/templates")
    return TemplateEngine(config)

async def test_template_rendering(template_engine):
    content = await template_engine.render(
        "welcome.html",
        {"name": "User"}
    )
    assert "Welcome, User!" in content

async def test_template_filters(template_engine):
    content = await template_engine.render(
        "product.html",
        {"price": 99.99}
    )
    assert "€99.99" in content

async def test_context_middleware(client: TestClient):
    response = await client.get("/")
    assert response.status_code == 200
    assert "Welcome, Test User!" in response.text