Menu
Public repository / f1f33c69567a554b6ce011822661d3289c1e9c77
AkurAI-Framework / site/content/docs/loaders.md
Latest change 6452dc30cd3f5df3bc6c241143d70f89365547e9 - feat(cli): declarative per-route data loaders by Ólafur Búi Ólafsson
# Data loaders
A **data loader** lets a route name a JSON file whose contents are loaded into
the page's render context — so a data-driven page needs **no handler code**. It
is the declarative first step toward server `load` functions and
collection-backed loaders.
Loaders are opt-in and fully backward compatible — a route without a `data`
field renders exactly as it did before.
## Declaring a loader
Add an optional `"data"` field to a route in `backend/routes.json`. Its value is
the path of a JSON file **relative to `backend/`**:
```json
{
"routes": [
{ "path": "/", "template": "index" },
{
"path": "/team",
"template": "team",
"data": "data/team.json"
}
]
}
```
You can combine `data` with `layout` and the SEO `title`/`description` fields on
the same route. Existing fields keep working untouched.
## The merge rule
Before the template renders, the named file is parsed and merged into the
context:
1. If the loaded JSON is an **object**, its top-level keys are spread at the root
of the context, so a template reads them directly: `{{ heading }}`,
`{% for m in members %}`. The reserved `params` key is never overwritten.
2. The whole loaded document is **also** exposed under a `data` key, so a
template can reach it explicitly with `{{ data.heading }}` or iterate
`{% for m in data.members %}`. This is the only access path when the file's
top level is an array or scalar rather than an object.
The project-wide `page.json` context, route `params`, and per-route `meta_*`
fields are all still present; the loader only **adds** to the context.
## Path safety
The `data` path is resolved strictly **under `backend/`**. Any `..` or `.`
segment (or an empty name) is rejected, so a route can never read a file outside
the project's backend directory. A missing file or malformed JSON is treated as
"no data" — the page still renders, with the loaded values simply absent, rather
than crashing the server.
## Iterating loaded data
With `data/team.json` shaped like:
```json
{
"heading": "Meet the team",
"members": [
{ "name": "Óli", "role": "Framework lead" },
{ "name": "Ada", "role": "Template engineer" }
]
}
```
the `team` template renders the list with the engine's `{% for %}` tag and
HTML-escaped `{{ }}` interpolation:
```html
<h1>{{ heading }}</h1>
<section class="features">
{% for m in members %}
<article class="feature">
<h3>{{ m.name }}</h3>
<p>{{ m.role }}</p>
</article>
{% endfor %}
</section>
```
No handler, no controller — the route declaration is the whole wiring.
## Live demo
The site itself ships a working example: the [`/team`](/team) route loads
`backend/data/team.json` and renders it through the `team` template.
## Where this is heading
Naming a static JSON file is the declarative entry point. The same `data` field
is the seam for richer loaders: server `load` functions that compute the context
per request, and **collection-backed** loaders that pull rows from the
framework's built-in store. The template contract — "the route supplies the
context, the page just renders it" — stays the same as loaders grow.