Templates Core

Nimbus Templates (.nimbus)

Nimbus includes its own lightweight, production-grade template engine featuring an Edge/Blade-inspired syntax. Template files use the .nimbus extension and reside in the resources/views/ directory. Compiled down to Go's native html/template, they deliver absolute safety against XSS attacks combined with a developer-friendly syntax.

§ Overview

Edge/Blade syntax allows you to compose clean HTML markup on the server and stream it to the client. Since templates execute on the server-side, they integrate natively with authentication guards, authorization checks, sessions, and request helper contexts.

Templates are automatically compiled at startup, and in development mode, they are refreshed on file change without requiring a server reboot.

§ Your First Template

Let's create a simple page to display a list of blog posts. This example demonstrates the fundamental workflow of rendering templates in Nimbus.

1. Create the Template File

Templates are stored in the resources/views/ directory. Create a new template file at resources/views/pages/posts/index.nimbus:

@layout('layout-app')

<div class="max-w-xl mx-auto py-8">
  <h1 class="text-2xl font-bold mb-6">Blog Posts</h1>
  
  @each(post in posts)
    <div class="mb-6 border-b pb-4">
      <h2 class="text-xl font-semibold text-slate-900 dark:text-white">
        {{ post.Title }}
      </h2>
      <p class="text-slate-600 mt-2">
        {{{ excerpt(post.Content, 280) }}}
      </p>
    </div>
  @end
</div>

Understanding the Syntax:

  • @layout('layout-app') inherits the global HTML shell (boilerplate, head, navigation).
  • @each(post in posts) ... @end loops over a Go slice or array.
  • {{ post.Title }} outputs HTML-escaped text.
  • {{{ excerpt(post.Content, 280) }}} outputs unescaped HTML content (use only with trusted content).

2. Register the Route and Controller

Register a route and serve it from your controller handler in Go:

package start

import (
    "github.com/CodeSyncr/nimbus/router"
    "app/controllers"
)

func RegisterRoutes(r *router.Router) {
    r.Get("/posts", controllers.PostsIndex)
}

Render the template from the controller by passing the view path and state:

package controllers

import (
    "github.com/CodeSyncr/nimbus/http"
    "app/models"
)

func PostsIndex(c *http.Context) error {
    posts, err := models.GetRecentPosts()
    if err != nil {
        return err
    }

    // Render resources/views/pages/posts/index.nimbus
    return c.View("pages/posts/index", map[string]any{
        "posts": posts,
    })
}

§ Understanding Template State

The data map you pass to c.View(name, state) is called the template state. All keys in this map become available as variables inside the template context.

In addition to explicit data variables, Nimbus shares framework globals with every template:

  • request: Access URL query parameters, headers, or client IP.
  • auth: Check authentication state and active user claims.
  • session: Retrieve flash data, messages, or session store variables.
  • Built-in helper functions (e.g. route(), csrfField(), excerpt()).

§ Template Syntax Refresher

Outputting Variables

<p>{{ user.Name }}</p>

Outputting Unescaped HTML

<div>{{{ post.BodyHtml }}}</div>

Conditionals

@if(user.IsAdmin)
  <span class="badge">Admin</span>
@elseif(user.IsModerator)
  <span class="badge">Moderator</span>
@else
  <span class="badge">User</span>
@end

Loops

@each(item in items)
  <li>{{ item }}</li>
@end

Evaluating Logic & Expressions

{{ len(items) > 0 ? "Items available" : "No items" }}