Templating Engine
Novaxjs2 includes a powerful templating system with two rendering modes: HTML
templates with custom syntax and JavaScript
templates for maximum flexibility and control.
Setting Up Views
// Configure view engine (HTML mode)
app.setViewEngine('novax', {
viewsPath: './views', // Directory containing templates
viewsType: 'html', // 'html' or 'js'
helpers: { // Custom helper functions
formatDate: (date) => new Date(date).toLocaleDateString(),
uppercase: (str) => str.toUpperCase()
}
});
// Or for JavaScript templates:
app.setViewEngine('novax', {
viewsType: 'js', // Uses JavaScript files for templates
viewsPath: './templates'
});
// Add custom helpers programmatically
app.addHelper('reverse', (str) => str.split('').reverse().join(''));
app.addHelper('currency', (amount) => `$${amount.toFixed(2)}`);
// Add multiple helpers at once
app.addHelpers({
truncate: (str, length) => str.length > length ? str.substring(0, length) + '...' : str,
pluralize: (count, singular, plural) => count === 1 ? singular : plural
});
HTML Templates
HTML templates use a custom syntax with double curly braces for expressions and special tags for logic.
Basic Syntax
<!-- views/home.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ heading }}</h1>
<p>Current time: {{ new Date().toLocaleTimeString() }}</p>
<p>Formatted date: {{ formatDate(createdAt) }}</p>
</body>
</html>
Conditional Logic
<!-- If/Else statements -->
{{#if user.isAdmin}}
<div class="admin-panel">Admin controls</div>
{{#else}}
<p>Regular user view</p>
{{/if}}
<!-- Complex conditions -->
{{#if (user.age >= 18) && (user.status === 'active')}}
<p>Adult active user</p>
{{/if}}
<!-- Else if -->
{{#if score >= 90}}
<p>Grade: A</p>
{{#elif score >= 80}}
<p>Grade: B</p>
{{#else}}
<p>Grade: C</p>
{{/if}}
Loops
<!-- Basic loop -->
<ul>
{{#each items}}
<li>{{ name }} - ${{ price }}</li>
{{/each}}
</ul>
<!-- Accessing array index -->
{{#each items}}
<div class="item-{{ index }}">
<h3>{{ name }}</h3>
<p>Position: {{ index }} of {{ this.length }}</p>
</div>
{{/each}}
<!-- Accessing array items by index -->
{{#each items}}
<p>Next item: {{ this[index+1]?.name }}</p>
{{/each}}
Nested Data & Object Access
<!-- Nested object access -->
<p>Shipping to: {{ user.address.city }}, {{ user.address.country }}</p>
<!-- Array access -->
<p>First item: {{ items[0].name }}</p>
<!-- JSON output -->
<script>
const userData = {{ JSON.stringify(user) }};
</script>
<!-- Using helpers -->
<p>Price: {{ currency(product.price) }}</p>
<p>{{ truncate(description, 100) }}</p>
<p>{{ pluralize(items.length, 'item', 'items') }}</p>
JavaScript Templates (CommonJS style)
For more complex logic, you can use JavaScript templates that export a function or string:
// views/home.js
module.exports = function(data) {
// Complex logic here
const welcomeMessage = data.user.isAdmin
? 'Welcome Administrator'
: `Welcome ${data.user.name}`;
// Helper function within template
function renderItems(items) {
return items.map(item => `
<div class="item">
<h3>${item.name}</h3>
<p>Price: $${item.price.toFixed(2)}</p>
<p>${item.description}</p>
</div>
`).join('');
}
return `
<!DOCTYPE html>
<html>
<head>
<title>${data.title}</title>
<style>
body { font-family: Arial, sans-serif; }
.item { border: 1px solid #ccc; padding: 1rem; margin: 1rem 0; }
</style>
</head>
<body>
<h1>${welcomeMessage}</h1>
${renderItems(data.items)}
<footer>
<p>Generated on ${new Date().toLocaleDateString()}</p>
</footer>
</body>
</html>
`;
};
Rendering Templates
// Basic rendering
app.get('/', async (req, res) => {
const data = {
title: 'Home Page',
user: {
name: 'John Doe',
isAdmin: false,
address: {
city: 'New York',
country: 'USA'
}
},
items: [
{ name: 'Product 1', price: 19.99, description: 'A great product' },
{ name: 'Product 2', price: 29.99, description: 'Another great product' }
],
createdAt: new Date()
};
const html = await app.render('home', data);
return html;
});
// With error handling
app.get('/profile', async (req, res) => {
try {
const userData = await getUserData(req.user.id);
return await app.render('profile', userData);
} catch (err) {
return await app.render('error', {
message: 'Failed to load profile',
error: err.message
});
}
});
// Using with other response methods
app.get('/api/user/:id', async (req, res) => {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).render('404', { id: req.params.id });
}
return res.render('user-profile', { user });
});
Template Reference
Feature | Syntax | Description |
---|---|---|
Variables | {{ variable }} | Output variable value |
Expressions | {{ user.name }} {{ items[0].price }} | JavaScript-like expressions |
Conditionals | {{#if condition}}...{{/if}} | Conditional rendering |
Else/Elif | {{#else}} {{#elif condition}} | Else clauses for conditionals |
Loops | {{#each array}}...{{/each}} | Iterate over arrays with index available |
Array Access | {{ this[index] }} | Access array items by index in loops |
JSON Output | {{ JSON.stringify(data) }} | Convert objects to JSON |
Helpers | {{ helperName(arg) }} | Use custom helper functions |
JavaScript | module.exports = function(data) {...} | Full JavaScript templates when using viewsType: 'js' |
Advanced Features
// Using third-party view engines
const pug = require('pug');
app.setViewEngine(pug, {
viewsPath: './views',
viewsType: 'pug',
engineOptions: {
pretty: true
}
});
// Async helpers
app.addHelper('fetchData', async (url) => {
const response = await fetch(url);
return await response.json();
});
// Template inheritance (using JavaScript templates)
// base-template.js
module.exports = function(data, content) {
return `
<!DOCTYPE html>
<html>
<head>
<title>${data.title}</title>
</head>
<body>
<header>...</header>
<main>${content}</main>
<footer>...</footer>
</body>
</html>
`;
};
// page.js
module.exports = async function(data) {
const baseTemplate = require('./base-template');
const pageContent = `<h1>${data.title}</h1><p>${data.content}</p>`;
return baseTemplate(data, pageContent);
};