Template Engine
Template Engine
Section titled “Template Engine”Harpia offers flexible template rendering with support for both third-party engines (like EJS) and its own high-performance native engine designed for modern web applications.
Third-Party Template Engine
Section titled “Third-Party Template Engine”Using EJS
Section titled “Using EJS”1. Create Engine Configuration
Section titled “1. Create Engine Configuration”import ejs from "ejs";import path from "node:path";import type { Harpia } from "harpia";
export const ejsEngine = { configure: (app: Harpia) => { app.engine.set(ejsEngine); }, render: async (view: string, data: Record<string, any>) => { const filePath = path.resolve(process.cwd(), "src/views", `${view}.ejs`); return await ejs.renderFile(filePath, data); }};2. Configure Application
Section titled “2. Configure Application”import harpia from "harpia";import { ejsEngine } from "./config/ejs";
const app = harpia();
ejsEngine.configure(app);
app.get("/books", async (req, res) => { await res.render("home", { title: "Books", books: [] });});
app.listen...3. EJS Template Example
Section titled “3. EJS Template Example”src/views/home.ejs:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><%= title %></title></head><body> <h1>Welcome to <%= title %></h1> <ul> <% books.forEach(book => { %> <li><%= book.title %></li> <% }); %> </ul></body></html>Harpia Native Template Engine
Section titled “Harpia Native Template Engine”The built-in engine provides a clean, HTML-based syntax with support for layouts, blocks, components, and plugins.
Configuration
Section titled “Configuration”Standard Structure (Non-Modular)
Section titled “Standard Structure (Non-Modular)”import path from "node:path";import { TemplateEngine } from "harpia/template-engine";
const baseDir = process.cwd();
export const engine = new TemplateEngine({ viewName: "page", useModules: false, fileExtension: ".html", // The default is `.html`, but you can use `.txt`, `.hml`, or any other. path: { views: path.join(baseDir, "src", "views"), layouts: path.join(baseDir, "src", "layouts"), // optional components: path.join(baseDir, "src", "components"), // optional },});
// Register custom pluginsengine.registerPlugin("uppercase", (str: string) => str.toUpperCase());engine.registerPlugin("formatDate", (date: Date) => date.toLocaleDateString());engine.registerPlugin("currency", (value: number) => `$${value.toFixed(2)}`);import harpia from "harpia";import { engine } from "./config/template-engine";
const app = harpia();engine.configure(app);
app.get("/products", async (req, res) => { await res.render("products", { title: "Our Products", products: [ { name: "Product A", price: 29.99 }, { name: "Product B", price: 39.99 } ] });});If you want to use the Security Header Protection:
import { harpia } from "harpia";import { shield } from "./shield";import { engine } from "./template-engine";
const app = harpia();
// Apply security headers middlewareapp.use(shield.middleware(app));
// Setup template engineengine.configure(app, shield.instance);
// Routesapp.get("/books", async (req, res) => { await res.render("home", { title: "Books" });});
app.listen...Modular Structure
Section titled “Modular Structure”If using modular routing, set useModules: true:
import path from "node:path";import { TemplateEngine } from "harpia/template-engine";
export const engine = new TemplateEngine({ viewName: "page", useModules: true, fileExtension: ".html", path: { views: path.join(process.cwd(), "modules", "**", "pages"), layouts: path.join(process.cwd(), "resources", "layouts"), components: path.join(process.cwd(), "resources", "components"), },});app.get("/users", async (req, res) => { await res.module("users").render("profile", { user: { name: "John", email: "john@example.com" } });});Manual Template Rendering
Section titled “Manual Template Rendering”Render templates anywhere in your application:
app.get("/", async (req, res) => { const content = await engine.generate( "app/services/mailer/templates/account-created", { data } );
return res.html(content);});Template Syntax Reference
Section titled “Template Syntax Reference”Variables & Output
Section titled “Variables & Output”<h1>{{ title }}</h1><p>Welcome, {{ user.name }}!</p><p>Price: {{ currency(product.price) }}</p>Local Variables
Section titled “Local Variables”@set welcomeMessage = "Hello, World!" @endset<p>{{ welcomeMessage }}</p>Conditionals
Section titled “Conditionals”@if user.isAdmin <div class="admin-panel"> <button>Edit</button> <button>Delete</button> </div>@elseif user.isEditor <button>Edit</button>@else <p>Regular user</p>@endif<!-- Array iteration -->@for product in products <div class="product"> <h3>{{ product.name }}</h3> <p>Price: {{ currency(product.price) }}</p> </div>@endfor
<!-- Object iteration -->@for [key, value] in settings <div class="setting"> <span class="key">{{ key }}:</span> <span class="value">{{ value }}</span> </div>@endforLayout System
Section titled “Layout System”Layout (layouts/default.html):
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>{{ title }} - My App</title></head><body> <header> <nav>...</nav> </header>
<main> @yield("content") </main>
<footer> @yield("footer") </footer></body></html>Page (views/products.html):
@layout("default", { title: "Products" })
@block("content") <h1>Our Products</h1>
@for product in products @component("product-card", product) @endfor@endblock
@block("footer") <p>Contact us for more information!</p>@endblockComponents
Section titled “Components”Component (components/product-card.html):
<div class="card"> <h3>{{ name }}</h3> <p class="price">{{ currency(price) }}</p> <button>Add to Cart</button></div>Usage:
@component("product-card", { name: "Premium Widget", price: 99.99})Imports
Section titled “Imports”@import("shared/header", { title: "Page Title" })
<div class="content"> <!-- page content --></div>
@import("shared/footer")Comments
Section titled “Comments”## This is a single-line comment
## This is a multi-line comment that won't appear in the output##Advanced Plugin Usage
Section titled “Advanced Plugin Usage”Complex Conditionals with Plugins
Section titled “Complex Conditionals with Plugins”// Register pluginsengine.registerPlugin("equals", (a: any, b: any) => a === b);engine.registerPlugin("greaterThan", (a: number, b: number) => a > b);engine.registerPlugin("and", (...args: boolean[]) => args.every(Boolean));@if and(equals(user.role, "admin"), greaterThan(user.experience, 2)) <div class="advanced-controls"> <button>Advanced Settings</button> <button>Export Data</button> </div>@endifPlugin Chains
Section titled “Plugin Chains”<p>{{ uppercase(trim(user.name)) }}</p><p>{{ currency(calculateDiscount(product.price, user.discount)) }}</p>Unescaped Output
Section titled “Unescaped Output”<div> {{ raw(htmlContent) }}</div>Nonce Value
Section titled “Nonce Value”When using the Shield module for security headers, the template engine automatically registers a generateNonce plugin. This plugin generates a unique nonce for each request, which you can use to secure inline scripts and styles for Content Security Policy (CSP).
@set nonce = generateNonce() @endset
<script nonce="{{ nonce }}"> console.log("This script is CSP-compliant. Count: 1");</script>
<script nonce="{{ nonce }}"> console.log("This script is CSP-compliant. Count: 2");</script>The
generateNonceplugin is only available when the Shield instance is passed to the engine.configure method. See the Shield section for setup instructions.
File Structure Examples
Section titled “File Structure Examples”Standard Structure
Section titled “Standard Structure”src/ views/ home/ page.html products/ page.html layouts/ default.html admin.html components/ header.html product-card.htmlModular Structure
Section titled “Modular Structure”modules/ users/ pages/ profile/ page.html settings/ page.html products/ pages/ listing/ page.html detail/ page.htmlresources/ layouts/ default.html components/ navigation.html footer.htmlBest Practices
Section titled “Best Practices”- Use layouts for consistent page structure
- Create reusable components for common UI elements
- Register plugins for data transformation logic
- Use the modular structure for large applications
- Keep templates focused on presentation logic
The native Harpia template engine provides a clean, intuitive syntax while maintaining powerful features for building dynamic web applications.