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)¶
Template (Handlebars)¶
<nav>
<ul>
{{#each ekdata.menu}}
<li>
<a href="{{this.href}}">{{this.label}}</a>
</li>
{{/each}}
</ul>
</nav>
Notes¶
labelcan be a multilingual fieldhrefshould 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)¶
Template (Handlebars)¶
<ul>
{{#each ekdata.directory}}
<li>
<a href="/detail?uid={{this.uid}}">
{{this.title}}
</a>
</li>
{{/each}}
</ul>
Notes¶
uidis 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)¶
Template (Handlebars)¶
Notes¶
- the view remains static
- content is selected using
query.uid uidmust 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¶
Datasource (YAML)¶
Notes¶
uidis a business identifier defined in each page record- it is managed in the datasource (not generated by routing)
- the same
uidcan 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¶
limitdefines 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: truegives the number of results (total.articles, filter included) and of pages (pages.articles);page.articlesis the page displayed — see Number of results and pages- show Previous with
hasPrev, notquery.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
filtermatches 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)¶
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.weatheris the response body, ornullif the call failed- every page view runs the Method: use
whento 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)¶
Script (assets/js/map.js)¶
(ekit.data.stores ?? []).forEach(store => {
addMarker(store.latitude, store.longitude, store.name);
});
Notes¶
- only datasources with
expose: trueare 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: checkresult.ok, thenresult.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.