=====================
Starlette Integration
=====================
Overview
========
The Starlette bridge provides a lightweight way to serve wilco components from
pure Starlette applications. It returns a list of Route objects that can be
mounted on any Starlette application.
Installation
============
Install wilco with Starlette support using the optional extra:
.. code-block:: bash
pip install wilco[starlette]
This installs wilco with Starlette (>= 0.40.0). For development, you'll also
want uvicorn:
.. code-block:: bash
pip install wilco[starlette] uvicorn
Quick start
===========
Here's a minimal example that serves components from a directory:
.. code-block:: python
from pathlib import Path
from starlette.applications import Starlette
from starlette.routing import Mount
from wilco import ComponentRegistry
from wilco.bridges.starlette import create_routes
# Create a registry pointing to your components
registry = ComponentRegistry(Path("./components"))
# Create wilco routes
routes = create_routes(registry)
# Mount on your Starlette app
app = Starlette(routes=[
Mount("/api", routes=routes),
])
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8400)
This creates three endpoints:
- ``GET /api/bundles`` - List available components
- ``GET /api/bundles/{name}.js`` - Get bundled JavaScript
- ``GET /api/bundles/{name}/metadata`` - Get component metadata
API reference
=============
create_routes
-------------
.. code-block:: python
from wilco.bridges.starlette import create_routes
def create_routes(
registry: ComponentRegistry,
build_dir: Path | None = None,
) -> list[Route]:
"""Create Starlette routes for component serving.
Args:
registry: The component registry to serve components from.
build_dir: Optional path to pre-built bundles directory.
When provided, serves pre-built bundles from the manifest
and falls back to live bundling for missing components.
Returns:
A list of Starlette Route objects that can be mounted on any app.
"""
The returned routes provide the following endpoints:
GET /bundles
^^^^^^^^^^^^
List all available component bundles.
**Response:**
.. code-block:: json
[
{"name": "counter"},
{"name": "product_card"},
{"name": "ui.button"}
]
GET /bundles/{name}.js
^^^^^^^^^^^^^^^^^^^^^^
Get the bundled JavaScript for a component.
**Parameters:**
- ``name``: Component name (e.g., ``counter``, ``ui.button``)
**Response:**
- Content-Type: ``application/javascript``
- Cache-Control: ``public, max-age=31536000, immutable``
The response contains the bundled ESM JavaScript with inline source maps.
**Errors:**
- ``404``: Component not found
- ``422``: Invalid component name
- ``500``: Bundling failed (esbuild error)
GET /bundles/{name}/metadata
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get metadata for a component, including its JSON schema and content hash.
**Parameters:**
- ``name``: Component name
**Response:**
.. code-block:: json
{
"title": "Counter",
"description": "A simple counter component",
"version": "1.0.0",
"properties": {
"initialValue": {"type": "number", "default": 0},
"step": {"type": "number", "default": 1}
},
"hash": "abc123def456"
}
The ``hash`` field can be used for cache busting on the frontend.
**Errors:**
- ``404``: Component not found
- ``422``: Invalid component name
Component registry
==================
Multiple sources
----------------
You can register components from multiple directories:
.. code-block:: python
registry = ComponentRegistry()
# Add components from multiple sources
registry.add_source(Path("./shared_components"))
registry.add_source(Path("./app_components"), prefix="app")
routes = create_routes(registry)
app = Starlette(routes=[
Mount("/api", routes=routes),
])
With a prefix, components are namespaced:
- ``./shared_components/button/`` becomes ``button``
- ``./app_components/header/`` becomes ``app:header``
Caching
=======
The Starlette bridge returns long-lived cache headers for component bundles:
.. code-block:: text
Cache-Control: public, max-age=31536000, immutable
To enable cache busting:
1. Fetch the metadata endpoint to get the ``hash``
2. Append the hash as a query parameter: ``/api/bundles/counter.js?abc123``
The hash changes whenever the component source changes.
Full example
============
Here's a complete example with CORS middleware:
.. code-block:: python
from pathlib import Path
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Mount, Route
from starlette.responses import JSONResponse
from wilco import ComponentRegistry
from wilco.bridges.starlette import create_routes
# Set up component registry
registry = ComponentRegistry()
registry.add_source(Path("./components"))
# Create wilco routes
wilco_routes = create_routes(registry)
def homepage(request):
return JSONResponse({"message": "Component server running"})
# Configure CORS for frontend development
middleware = [
Middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # Vite dev server
allow_methods=["GET"],
allow_headers=["*"],
)
]
app = Starlette(
routes=[
Route("/", homepage),
Mount("/api", routes=wilco_routes),
],
middleware=middleware,
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8400, reload=True)
Frontend integration
====================
From the frontend, you can load components using the wilco loader:
.. code-block:: tsx
import { useComponent } from '@wilcojs/react';
function App() {
const Counter = useComponent('counter');
return (