Skip to content

Common Patterns

This page presents common, recommended patterns for building dynamic websites with Ekit. All patterns are based on simple principles:

  • one stable view
  • dynamic data from datasources
  • language inferred from the URL
  • content driven by query parameters

These patterns work for monolingual and multilingual projects without modification.


Pattern 1 — Dynamic menu from database

Use case

Build a navigation menu managed entirely from the database, with multilingual labels.

Datasource (YAML)

---
datasources:
  menu:
    from: menu
    orderBy: position
---

Template (Handlebars)

<nav>
  <ul>
    {{#each ekdata.menu}}
      <li>
        <a href="{{this.href}}">{{this.label}}</a>
      </li>
    {{/each}}
  </ul>
</nav>

Notes

  • label can be a multilingual field
  • href should be a relative URL
  • language switching is handled automatically

Pattern 2 — Directory / listing page

Use case

Display a list of items (directory, products, articles, people, etc.).

Datasource (YAML)

---
datasources:
  directory:
    from: items
    limit: 20
---

Template (Handlebars)

<ul>
  {{#each ekdata.directory}}
    <li>
      <a href="/detail?uid={{this.uid}}">
        {{this.title}}
      </a>
    </li>
  {{/each}}
</ul>

Notes

  • uid is a logical identifier stored in each item
  • it is used to link a list item to its detail page

Pattern 3 — Detail page (one view, many contents)

Use case

Render multiple content pages using a single view.

Datasource (YAML)

---
datasources:
  item:
    from: items
    uid: "{{query.uid}}"
---

Template (Handlebars)

<h1>{{ekdata.item.title}}</h1>
<div>{{ekdata.item.content}}</div>

Notes

  • the view remains static
  • content is selected using query.uid
  • uid must exist on each item in the datasource
  • works naturally with HTTP cache

Pattern 4 — Multilingual landing pages

Use case

Create multiple presentation pages using one template and a logical identifier.

Example URLs

/fr/presentation?uid=home
/fr/presentation?uid=company
/en/presentation?uid=home

Datasource (YAML)

---
datasources:
  page:
    from: pages
    uid: "{{query.uid}}"
---

Notes

  • uid is a business identifier defined in each page record
  • it is managed in the datasource (not generated by routing)
  • the same uid can exist across multiple languages
  • content is resolved automatically based on the current language

Pattern 5 — Pagination and filtering

Use case

Paginate or filter results using query parameters.

Example URLs

/fr/articles
/fr/articles?cursor=…            (next page)
/fr/articles?cursor=…&back=true  (previous page)
/fr/articles?category=news

Datasource (YAML)

---
datasources:
  articles:
    from: articles
    limit: 10
    count: true
    sort:
      field: date
      dir: desc
    filter:
      field: category
      value: "{{query.category}}"
---

Template (Handlebars)

{{#if total.articles}}<p>{{total.articles}} articles</p>{{/if}}

{{#each ekdata.articles}}<article>{{this.title}}</article>{{/each}}

{{#if hasPrev.articles}}
  <a href="/{{lang}}/articles/?cursor={{jsonUrl prevCursors.articles}}&back=true{{#if query.category}}&category={{query.category}}{{/if}}">Previous</a>
{{/if}}
{{#if (gt pages.articles 1)}}<span>Page {{page.articles}} / {{pages.articles}}</span>{{/if}}
{{#if hasMore.articles}}
  <a href="/{{lang}}/articles/?cursor={{jsonUrl cursors.articles}}{{#if query.category}}&category={{query.category}}{{/if}}">Next</a>
{{/if}}

Notes

  • limit defines the number of items per page
  • pagination uses cursors from the URL — see Pagination
  • keep the other query parameters (here category) in the pagination links
  • count: true gives the number of results (total.articles, filter included) and of pages (pages.articles); page.articles is the page displayed — see Number of results and pages
  • show Previous with hasPrev, not query.cursor (the URL keeps a cursor when you come back to page 1), and write the page path before ?cursor=
  • filtering and pagination can be combined without changing the view
  • filter matches text containing the value (case-insensitive); an empty value disables the filter

Pattern 6 — Content from an external API (Method datasource)

Use case

Display data from a third-party API (weather, prices, AI answer…) rendered on the server, with the API key kept secret.

Datasource (YAML)

---
datasources:
  weather:
    type: method
    method: weather-now
    inputs:
      city: "{{params.slug}}"
---

Template (Handlebars)

{{#if ekdata.weather}}
  <p>{{ekdata.weather.current.temperature}} °C</p>
{{else}}
  <p>{{ektrans 'WEATHER_UNAVAILABLE'}}</p>
{{/if}}

Notes

  • the Method calls the API with its secrets; nothing reaches the browser
  • ekdata.weather is the response body, or null if the call failed
  • every page view runs the Method: use when to call it only when needed

Pattern 7 — Data for client-side scripts (ekit.data)

Use case

Reuse rendered data in JavaScript (map markers, charts, client-side sorting) without an extra request.

Datasource (YAML)

---
datasources:
  stores:
    from: stores
    limit: 100
    expose: true
---

Script (assets/js/map.js)

(ekit.data.stores ?? []).forEach(store => {
  addMarker(store.latitude, store.longitude, store.name);
});

Notes

  • only datasources with expose: true are sent to the browser — do not expose tables with private fields
  • the editor completes ekit.data. with the exposed datasources and their fields

Pattern 8 — Interactive call from the browser (ekit.methods.call)

Use case

Call an API when a visitor acts — a question form, a price simulator, an availability check.

Template (Handlebars)

<form id="ask">
  <input name="q" required>
  <button>{{ektrans 'ASK'}}</button>
</form>
<p id="answer"></p>
<script src='{{ekasset "js/ask.js"}}'></script>

Script (assets/js/ask.js)

document.getElementById('ask').addEventListener('submit', async (event) => {
  event.preventDefault();
  const prompt = new FormData(event.target).get('q');
  const result = await ekit.methods.call('ask-ai', { prompt });
  document.getElementById('answer').textContent = result.ok
    ? result.data.answer
    : 'Service unavailable, please retry later.';
});

Notes

  • the Method must be enabled with exposure public
  • call() never throws: check result.ok, then result.status / result.error.code
  • calls are rate limited; for a paid API, set a spending cap with the provider

Best practices

  • keep views stable
  • drive content with data, not routing
  • use logical identifiers (uid, slug) instead of database IDs
  • store navigation and structure in the database
  • use root-relative URLs in templates
  • let HTTP caching handle URL variations

Summary

Ekit patterns favor simplicity and flexibility:

  • unlimited pages
  • multilingual sites (languages per project depend on your plan)
  • no dynamic routing complexity
  • pagination handled at the datasource level
  • clear separation between structure and data

These patterns can be combined freely to build complex websites while keeping templates easy to maintain.