Skip to content

Views

The views block inside an entity defines HTML pages and their routes. Each view binds a query to a template, producing a dynamic HTML response.

entity "User" {
    views {
        template "/user" query="user_by_id" html="
            <h1>{{data.name}}</h1>
            <p>Email: {{data.email}}</p>
        "
    }
}

A view becomes an HTTP endpoint at the specified route. When the route is requested, Terranova executes the associated query and renders the template with the query result as the data context.


Syntax

template "<route>" [query="<query_name>"] [entity="<entity_name>"] [file="<path>"] [html="<inline_html>"]

Attributes

Attribute Type Required Description
route (name) string URL path (e.g., "/user"). The route is static; parameters are passed via query string (for GET/DELETE) or request body (for POST/PUT).
query string Name of a query (raw or composed) defined in the same entity or in another entity (see entity). If omitted, the template is static (no data binding).
entity string Name of the entity where the query is defined. Required when the query belongs to a different entity.
file string Path to an external HTML template file. Mutually exclusive with html.
html string Inline HTML template string. Mutually exclusive with file.

One of file or html must be present.

Note: template_set is not yet supported. Only individual template nodes are available.


Data Context for Templates

The data available in a template depends on the type of query used.

Raw Query

When a view uses a raw query, the query result is an array of rows. This array is placed under a top‑level key named data. Each row is an object with fields corresponding to the SQL result columns.

get "user_by_id" sql="SELECT id, name, email FROM user WHERE id = :id" {
    param "id" type="int"
}

views {
    template "/user" query="user_by_id" html="
        <h1>{{data.name}}</h1>
        <p>Email: {{data.email}}</p>
    "
}

When a client requests GET /user?id=123, the query receives :id = 123. Because the query returns a single row (by primary key), the data array contains one element. Access its fields using {{data.name}}.

For queries that return multiple rows, use a section to iterate:

get "all_users" sql="SELECT id, name FROM user"

views {
    template "/users" query="all_users" html="
        <ul>
        {{#data}}
            <li>{{name}}</li>
        {{/data}}
        </ul>
    "
}

Composed Query

When a view uses a composed query, the result is an object where each key corresponds to a data block in the composed query. Each of those values is itself an object containing a data array (the sub‑query’s rows), plus count, error, and modified.

get "user_with_posts" {
    param "id" type="int"
    data "user" query="user_by_id" { bind "id" }
    data "posts" query="posts_by_user" { bind "user_id" from="id" }
}

views {
    template "/user_posts" query="user_with_posts" html="
        <h1>{{user.data.name}}</h1>
        <h2>Posts</h2>
        <ul>
        {{#posts.data}}
            <li>{{title}}</li>
        {{/posts.data}}
        </ul>
    "
}

In this example:

  • user.data is an array of user rows (one element).

  • posts.data is an array of post rows.

Static Template (No Query)

If no query is specified, the template receives no data context. It is rendered as a static HTML page.

views {
    template "/about" html="<h1>About Us</h1><p>Welcome</p>"
}

Static templates are useful for informational pages, documentation, or forms that submit to API endpoints.


Parameter Binding

Parameters are passed to the query via:

  • URL query string for GET and DELETE requests.

  • JSON request body for POST and PUT requests.

The query parameter names must match the names declared in the query's param nodes.

Example:

get "user_by_id" sql="SELECT * FROM user WHERE id = :id" {
    param "id" type="int"
}

views {
    template "/user" query="user_by_id" html="<h1>{{data.name}}</h1>"
}

A request to GET /user?id=42 executes the query with :id = 42.

Note: Path parameters (e.g., /user/:id) are not supported for views. Always use query string parameters for dynamic values.


Using Queries from Another Entity

To use a query defined in a different entity, specify the entity attribute:

entity "User" {
    queries {
        get "profile" sql="SELECT * FROM user WHERE id = :id" {
            param "id" type="int"
        }
    }
}

entity "Admin" {
    views {
        template "/admin/user" query="profile" entity="User" html="
            <h1>{{data.name}}</h1>
        "
    }
}

External Template Files

For larger templates, use the file attribute to reference an external HTML file:

views {
    template "/user" query="user_by_id" file="templates/user_profile.html"
}

The file path is relative to the location of the .kdl specification file. Terranova watches these files for changes in development mode.


Template Syntax

Terranova implements a subset of Mustache. All features support dotted paths (nested variable access).

Feature Syntax Example Notes
Interpolation {{ variable }} {{user.data.name}} Dotted sequences (e.g., {{user.address.city}}) are supported.
Sections (iteration / truthy) {{#variable}} ... {{/variable}} {{#user.posts}} <li>{{title}}</li> {{/user.posts}} Dotted paths are supported. The section iterates over the nested array or object.
Inverted sections (falsey / empty) {{^variable}} ... {{/variable}} {{^user.posts}} <p>No posts.</p> {{/user.posts}} Dotted paths are supported.
Comments {{! comment }} {{! This will not appear in output }}
Delimiter change {{=<% %>=}} {{=<% %>=}}<% name %> Allows changing the tag delimiters.

Complete Example

application "Blog" version="0.1" {
    entity "User" {
        schema {
            pk "id" type="int"
            field "name" type="string"
            field "email" type="string"
        }
        queries {
            get "user_by_id" sql="SELECT * FROM user WHERE id = :id" {
                param "id" type="int"
            }
            get "all_users" sql="SELECT id, name FROM user"
        }
        views {
            template "/user" query="user_by_id" html="
                <!DOCTYPE html>
                <html>
                <head><title>{{data.name}}</title></head>
                <body>
                    <h1>{{data.name}}</h1>
                    <p>Email: {{data.email}}</p>
                </body>
                </html>
            "
            template "/users" query="all_users" html="
                <ul>
                {{#data}}
                    <li><a href='/user?id={{id}}'>{{name}}</a></li>
                {{/data}}
                {{^data}}
                    <li>No users registered yet.</li>
                {{/data}}
                </ul>
            "
            template "/about" html="<h1>About this Blog</h1>"
        }
    }

    profile "dev" default=true {
        listen address="127.0.0.1" port=8080
    }
}

What Terranova Does With Views

  • Route registration – Each template creates an HTTP endpoint at the specified static route.
  • Query execution – If query is provided, the query runs before rendering, and its result becomes the template context.
  • Parameter binding – Parameters are taken from the request's query string (for GET/DELETE) or JSON body (for POST/PUT). Path parameters are not supported.
  • Data context structure:
  • Raw query → context contains a top‑level data array.
  • Composed query → context contains keys named after each data block, each with a data array (and count, error, modified).
  • Template rendering – The template (inline or external) is rendered with the query result using a subset of Mustache:
  • Interpolation supports dotted paths.
  • Sections and inverted sections support dotted paths.
  • Comments and delimiter changes are supported.
  • Static views – Views without a query return the template as‑is, with no data binding.

See Also