Content Handling
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)
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.
Zero Dependencies • Fast • Lightweight
- 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 Templatestypescript
1import { Moro, middleware } from '@morojs/moro';
2
3const app = new Moro();
4
5// Enable Moro template engine (default)
6app.use(middleware.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 Examplestypescript
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 Templatestypescript
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 Componentstypescript
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 Filterstypescript
1import { middleware } from '@morojs/moro';
2
3app.use(middleware.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 Posttypescript
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 Handlebarstypescript
1// Install: npm install handlebars
2import { middleware } from '@morojs/moro';
3
4app.use(middleware.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 EJStypescript
1// Install: npm install ejs
2import { middleware } from '@morojs/moro';
3
4app.use(middleware.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