AkurAI Build
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.