EJS
EJS (Embedded JavaScript) is the most approachable template engine: a .ejs file is just HTML with JavaScript embedded in <% %> tags. There's no new language to learn — you write the markup you already know and drop in real JS expressions for the dynamic parts. This page covers the tag syntax, escaping, loops and conditionals, and how to structure pages with partials.
Setup
npm install ejs
app.js
import express from 'express'
const app = express()
app.set('view engine', 'ejs') // files end in .ejs
app.set('views', './views') // template directory
app.get('/users/:id', async (req, res) => {
const user = await db.users.find(req.params.id)
if (!user) return res.status(404).render('404')
res.render('users/show', { user }) // renders views/users/show.ejs
})The EJS tags
Tag | Purpose |
|---|---|
| Output, HTML-escaped (safe default) |
| Output, raw/unescaped (dangerous with user data) |
| Run JS, no output (loops, ifs, assignments) |
| Comment, not rendered |
| Include a partial template |
| Whitespace-trimming variants |
A complete template
views/users/show.ejs
<!DOCTYPE html>
<html>
<head><title><%= user.name %></title></head>
<body>
<%- include('../partials/header') %>
<h1>Welcome, <%= user.name %></h1> <!-- escaped: safe -->
<p>Email: <%= user.email %></p>
<% if (user.isAdmin) { %>
<a href="/admin">Admin panel</a>
<% } else { %>
<p>Standard account</p>
<% } %>
<h2>Recent orders</h2>
<ul>
<% user.orders.forEach((order) => { %>
<li><%= order.title %> — $<%= order.total.toFixed(2) %></li>
<% }) %>
</ul>
<% if (user.orders.length === 0) { %>
<p>No orders yet.</p>
<% } %>
<%- include('../partials/footer') %>
</body>
</html>Passing data — and handling missing values
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
notifications: notifications ?? [], // pass a default, not undefined
})res.locals for data shared across views
// Middleware: make these available to EVERY render automatically.
app.use((req, res, next) => {
res.locals.currentUser = req.user
res.locals.siteName = 'Acme'
next()
})
// Now any template can use <%= siteName %> without it being passed each time.Partials with parameters
Passing data into an include
<!-- views/partials/card.ejs -->
<div class="card">
<h3><%= title %></h3>
<p><%= body %></p>
</div>
<!-- using it, passing locals: -->
<%- include('partials/card', { title: 'Hello', body: 'World' }) %>When EJS fits
You want to write HTML you already know, with minimal new syntax.
You like having full JavaScript available in templates.
You're generating emails, server-rendered pages, or admin views.
You prefer Pug if you want terser, indentation-based markup, or Handlebars if you want to restrict logic in templates.