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

FeatureSyntaxDescription
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
JavaScriptmodule.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);
};