Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

Template Rendering

Powerful built-in Moro template engine with zero dependencies. Also supports Handlebars and EJS with layouts, partials, and custom helpers.

Moro Template Engine (Built-in)

Zero Dependencies • Fast • Lightweight

Moro's built-in template engine requires no external packages. It provides Handlebars-like syntax with variables, loops, conditionals, partials, and layouts out of the box.

  • • No external dependencies
  • • Fast rendering with optional caching
  • • Layouts and partials support
  • • Auto-escapes HTML by default
  • • Custom helpers and filters
  • • Familiar Handlebars-like syntax

Quick Start - Moro Templates

typescript

1import { Moro, template } from '@morojs/moro';
2
3const app = new Moro();
4
5// Enable Moro template engine (default)
6app.use(template({
7  views: './views',
8  engine: 'moro', // or omit - moro is default
9  cache: true,
10  defaultLayout: 'main'
11}));
12
13// Render a template
14app.get('/', (req, res) => {
15  res.render('index', {
16    title: 'Welcome to Moro',
17    user: { name: 'John Doe', role: 'admin' },
18    items: ['Fast', 'Simple', 'Powerful']
19  });
20});

Moro Template Syntax - Full Examples

typescript

1<!-- views/index.html -->
2<!DOCTYPE html>
3<html>
4<head>
5  <title>{{title}}</title>
6</head>
7<body>
8  <!-- Variables -->
9  <h1>{{title}}</h1>
10  <p>Welcome, {{user.name}}!</p>
11  
12  <!-- Raw HTML (unescaped) -->
13  <div>{{{rawHtmlContent}}}</div>
14  
15  <!-- Conditionals -->
16  {{#if user}}
17    <p>Role: {{user.role}}</p>
18    {{#if user.role === 'admin'}}
19      <button>Admin Panel</button>
20    {{else}}
21      <button>User Dashboard</button>
22    {{/if}}
23  {{else}}
24    <a href="/login">Please log in</a>
25  {{/if}}
26  
27  <!-- Loops -->
28  <ul>
29    {{#each items}}
30      <li>{{this}}</li>
31    {{/each}}
32  </ul>
33  
34  <!-- Loops with index and object properties -->
35  {{#each users}}
36    <div class="user-{{@index}}">
37      <h3>{{name}}</h3>
38      <p>{{email}}</p>
39    </div>
40  {{/each}}
41  
42  <!-- Partials -->
43  {{> header}}
44  {{> nav activeTab="home"}}
45  
46  <main>
47    <!-- Content here -->
48  </main>
49  
50  {{> footer}}
51</body>
52</html>

Layouts with Moro Templates

typescript

1<!-- views/layouts/main.html -->
2<!DOCTYPE html>
3<html lang="en">
4<head>
5  <meta charset="UTF-8">
6  <title>{{title}} - My App</title>
7  <link rel="stylesheet" href="/css/styles.css">
8</head>
9<body>
10  {{> header}}
11  
12  <main>
13    {{{body}}}  <!-- Page content renders here -->
14  </main>
15  
16  {{> footer}}
17  <script src="/js/app.js"></script>
18</body>
19</html>
20
21<!-- views/index.html (uses layout) -->
22<h1>{{pageTitle}}</h1>
23<p>This content will be injected into the layout's {{{body}}}</p>
24
25<!-- Route handler -->
26app.get('/page', (req, res) => {
27  res.render('index', {
28    title: 'Home',
29    pageTitle: 'Welcome Home',
30    layout: 'main' // specify layout
31  });
32});

Partials - Reusable Components

typescript

1<!-- views/partials/header.html -->
2<header>
3  <h1>{{siteName}}</h1>
4  <nav>
5    <a href="/">Home</a>
6    <a href="/about">About</a>
7  </nav>
8</header>
9
10<!-- views/partials/user-card.html -->
11<div class="user-card">
12  <img src="{{avatar}}" alt="{{name}}">
13  <h3>{{name}}</h3>
14  <p>{{bio}}</p>
15</div>
16
17<!-- Use partials in templates -->
18{{> header siteName="MoroJS"}}
19
20{{#each users}}
21  {{> user-card}}
22{{/each}}

Custom Helpers and Filters

typescript

1import { template } from '@morojs/moro';
2
3app.use(template({
4  views: './views',
5  engine: 'moro',
6  helpers: {
7    // String helpers
8    uppercase: (str: string) => str.toUpperCase(),
9    lowercase: (str: string) => str.toLowerCase(),
10    truncate: (str: string, length: number) => 
11      str.length > length ? str.slice(0, length) + '...' : str,
12    
13    // Date helpers
14    formatDate: (date: Date) => date.toLocaleDateString(),
15    timeAgo: (date: Date) => {
16      const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
17      if (seconds < 60) return 'just now';
18      if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
19      if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
20      return `${Math.floor(seconds / 86400)}d ago`;
21    },
22    
23    // Comparison helpers
24    eq: (a: any, b: any) => a === b,
25    ne: (a: any, b: any) => a !== b,
26    gt: (a: number, b: number) => a > b,
27    lt: (a: number, b: number) => a < b,
28    
29    // Utility helpers
30    json: (obj: any) => JSON.stringify(obj, null, 2),
31    length: (arr: any[]) => arr.length,
32    join: (arr: string[], separator = ', ') => arr.join(separator)
33  }
34}));
35
36// Use helpers in templates
37// <p>{{uppercase name}}</p>
38// <p>{{formatDate createdAt}}</p>
39// <p>{{truncate description 100}}</p>
40// {{#if eq user.role "admin"}}
41//   <button>Admin</button>
42// {{/if}}

Complete Example - Blog Post

typescript

1// Route handler
2app.get('/post/:id', async (req, res) => {
3  const post = await db.posts.findById(req.params.id);
4  const comments = await db.comments.findByPostId(req.params.id);
5  
6  res.render('post', {
7    title: post.title,
8    post,
9    comments,
10    siteName: 'My Blog',
11    user: req.user
12  });
13});
14
15<!-- views/post.html -->
16<article>
17  <header>
18    <h1>{{post.title}}</h1>
19    <p class="meta">
20      By {{post.author}}{{formatDate post.createdAt}}{{timeAgo post.createdAt}}
21    </p>
22  </header>
23  
24  <div class="content">
25    {{{post.content}}}
26  </div>
27  
28  <section class="comments">
29    <h2>Comments ({{length comments}})</h2>
30    
31    {{#if comments}}
32      {{#each comments}}
33        <div class="comment">
34          <strong>{{author}}</strong>
35          <span>{{timeAgo createdAt}}</span>
36          <p>{{text}}</p>
37        </div>
38      {{/each}}
39    {{else}}
40      <p>No comments yet. Be the first!</p>
41    {{/if}}
42    
43    {{#if user}}
44      <form method="POST" action="/post/{{post.id}}/comment">
45        <textarea name="comment" placeholder="Add a comment..."></textarea>
46        <button type="submit">Post Comment</button>
47      </form>
48    {{else}}
49      <p><a href="/login">Log in</a> to comment</p>
50    {{/if}}
51  </section>
52</article>

Alternative Template Engines

While Moro's built-in engine covers most use cases, you can also use Handlebars or EJS if needed. These require installing additional packages.

Using Handlebars

typescript

1// Install: npm install handlebars
2import { template } from '@morojs/moro';
3
4app.use(template({
5  views: './views',
6  engine: 'handlebars',
7  cache: process.env.NODE_ENV === 'production',
8  helpers: {
9    uppercase: (str: string) => str.toUpperCase(),
10    formatDate: (date: Date) => date.toLocaleDateString(),
11    eq: (a: any, b: any) => a === b
12  }
13}));
14
15// Handlebars template (views/index.hbs)
16// <h1>{{title}}</h1>
17// <p>{{user.name}}</p>
18// {{#each items}}
19//   <li>{{name}}</li>
20// {{/each}}

Using EJS

typescript

1// Install: npm install ejs
2import { template } from '@morojs/moro';
3
4app.use(template({
5  views: './views',
6  engine: 'ejs',
7  cache: true
8}));
9
10// EJS template (views/index.ejs)
11// <h1><%= title %></h1>
12// <p><%= user.name %></p>
13// <% items.forEach(item => { %>
14//   <li><%= item.name %></li>
15// <% }); %>

Best Practices

Do

  • • Enable template caching in production
  • • Use layouts for consistent structure
  • • Create reusable partials
  • • Escape user input in templates
  • • Use helpers for complex logic
  • • Organize templates by feature

Don't

  • • Put complex logic in templates
  • • Disable caching in production
  • • Trust user input without escaping
  • • Mix template engines
  • • Create deeply nested templates
  • • Skip template validation

Related Features