Skip to main content

Express EJS Integration

Introduction

When building web applications with Express.js, you often need to generate dynamic HTML content that changes based on data or user interactions. This is where templating engines come in handy, and EJS (Embedded JavaScript) is one of the most popular choices for Express applications.

EJS allows you to embed JavaScript directly in your HTML templates, making it intuitive for developers who already know HTML and JavaScript. In this tutorial, we'll learn how to integrate EJS with Express and create dynamic web pages.

What is EJS?

EJS stands for Embedded JavaScript. It's a simple templating language that lets you generate HTML markup with plain JavaScript. Unlike other templating engines, EJS uses plain JavaScript for logic, so there's no new syntax to learn if you already know JavaScript.

Key features of EJS:

  • Fast compilation and rendering
  • Simple syntax: just use <% %> to embed JavaScript
  • Both server-side and client-side support
  • Static caching of intermediate JavaScript
  • Includes for template partials

Setting Up Express with EJS

Step 1: Install the Required Packages

First, let's create a new Node.js project and install Express and EJS:

bash
mkdir express-ejs-app
cd express-ejs-app
npm init -y
npm install express ejs

Step 2: Create the Basic Express Application

Now, let's create our main server file (app.js):

javascript
const express = require('express');
const path = require('path');

const app = express();
const port = 3000;

// Configure the view engine and views directory
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// Serve static files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) => {
res.render('index', { title: 'Home Page', message: 'Welcome to Express with EJS!' });
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

Step 3: Create the EJS Template

Create a views directory in your project folder and add an index.ejs file:

html
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1><%= message %></h1>
<p>This is a basic EJS template rendered with Express.</p>

<% if (message.includes('Welcome')) { %>
<p>Thank you for visiting our site!</p>
<% } %>

<footer>© <%= new Date().getFullYear() %> My Express App</footer>
</body>
</html>

Step 4: Add Some CSS (Optional)

Create a public/css directory and add a style.css file:

css
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
line-height: 1.6;
}

h1 {
color: #333;
}

footer {
margin-top: 30px;
color: #666;
font-size: 0.8rem;
}

Step 5: Run Your Application

bash
node app.js

Now, visit http://localhost:3000 in your browser to see your EJS template rendered.

Understanding EJS Tags

EJS provides several tags for different purposes:

  1. <% %>: For control flow (no output)
  2. <%= %>: Outputs the value (HTML escaped)
  3. <%- %>: Outputs the unescaped value
  4. <%# %>: Comment tag (no execution, no output)
  5. <%_ %>: Whitespace slurping, removes all whitespace before it
  6. <%% %>: Outputs a literal '<%'
  7. %>: Plain ending tag
  8. -%>: Trim-mode ('newline slurp')
  9. _%>: Whitespace slurping, removes all whitespace after it

Let's see examples of some of these tags in action:

html
<!-- Control flow with <% %> -->
<% if (user) { %>
<h2>Hello, <%= user.name %></h2>
<% } else { %>
<h2>Please log in</h2>
<% } %>

<!-- Looping with <% %> -->
<ul>
<% for(let i = 0; i < items.length; i++) { %>
<li><%= items[i] %></li>
<% } %>
</ul>

<!-- Including HTML content using <%- %> -->
<%- include('partials/header') %>

<!-- Comment that won't appear in the output -->
<%# This is a comment that won't be rendered %>

Working with Data in EJS Templates

Let's expand our example by passing more complex data to our templates:

Update your app.js:

javascript
app.get('/users', (req, res) => {
const users = [
{ name: 'John', email: '[email protected]', active: true },
{ name: 'Jane', email: '[email protected]', active: false },
{ name: 'Bob', email: '[email protected]', active: true },
];

res.render('users', {
title: 'User List',
users: users,
showInactive: true
});
});

Create a new file views/users.ejs:

html
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1><%= title %></h1>

<table border="1">
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
</tr>

<% users.forEach(function(user) { %>
<% if (showInactive || user.active) { %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= user.active ? 'Active' : 'Inactive' %></td>
</tr>
<% } %>
<% }); %>
</table>

<p>
<% const activeUsers = users.filter(user => user.active); %>
Active users: <%= activeUsers.length %> out of <%= users.length %>
</p>

<footer>© <%= new Date().getFullYear() %> My Express App</footer>
</body>
</html>

Creating Reusable Template Partials

EJS allows you to create reusable template partials, which are great for elements like headers, footers, and navigation menus.

Let's create a folder for our partials:

bash
mkdir views/partials

Now, create views/partials/header.ejs:

html
<!DOCTYPE html>
<html>
<head>
<title><%= typeof title !== 'undefined' ? title : 'Express EJS App' %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<nav>
<a href="/">Home</a> |
<a href="/users">Users</a>
</nav>

Create views/partials/footer.ejs:

html
  <footer>© <%= new Date().getFullYear() %> My Express App</footer>
</body>
</html>

Now update your templates to use these partials. For example, modify views/index.ejs:

html
<%- include('partials/header') %>

<h1><%= message %></h1>
<p>This is a basic EJS template rendered with Express.</p>

<% if (message.includes('Welcome')) { %>
<p>Thank you for visiting our site!</p>
<% } %>

<%- include('partials/footer') %>

Building a Complete Example: Todo Application

Let's build a simple to-do list application to demonstrate EJS with Express in a more practical way.

Update your app.js:

javascript
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

// Configure middleware
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: false }));

// In-memory todo storage
let todos = [
{ id: 1, text: 'Learn Express.js', completed: false },
{ id: 2, text: 'Master EJS templating', completed: false },
{ id: 3, text: 'Build a project', completed: true },
];

// Routes
app.get('/', (req, res) => {
res.render('todo', {
title: 'Todo App',
todos: todos
});
});

// Add a new todo
app.post('/todo/add', (req, res) => {
const newTodo = {
id: todos.length + 1,
text: req.body.todoText,
completed: false
};

todos.push(newTodo);
res.redirect('/');
});

// Toggle todo status
app.post('/todo/toggle/:id', (req, res) => {
const id = parseInt(req.params.id);

todos = todos.map(todo => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});

res.redirect('/');
});

app.listen(port, () => {
console.log(`Todo app running at http://localhost:${port}`);
});

Don't forget to install the body-parser package:

bash
npm install body-parser

Now create views/todo.ejs:

html
<%- include('partials/header') %>

<h1>Todo List</h1>

<form action="/todo/add" method="POST">
<input type="text" name="todoText" placeholder="Add a new todo" required>
<button type="submit">Add</button>
</form>

<ul class="todo-list">
<% if (todos.length === 0) { %>
<li>No todos yet! Add one above.</li>
<% } else { %>
<% todos.forEach(function(todo) { %>
<li class="<%= todo.completed ? 'completed' : '' %>">
<%= todo.text %>
<form action="/todo/toggle/<%= todo.id %>" method="POST" style="display:inline">
<button type="submit" class="toggle-btn">
<%= todo.completed ? 'Mark Incomplete' : 'Mark Complete' %>
</button>
</form>
</li>
<% }); %>
<% } %>
</ul>

<p>You have <%= todos.filter(t => !t.completed).length %> incomplete todos</p>

<%- include('partials/footer') %>

Add some CSS to make it look better in public/css/style.css:

css
/* Add to existing styles */
.todo-list {
list-style-type: none;
padding: 0;
}

.todo-list li {
padding: 10px;
margin-bottom: 5px;
border: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}

.todo-list li.completed {
background-color: #f8f8f8;
text-decoration: line-through;
color: #777;
}

.toggle-btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
}

.toggle-btn:hover {
background-color: #45a049;
}

form {
margin-bottom: 20px;
}

input[type="text"] {
padding: 8px;
width: 70%;
margin-right: 10px;
}

button[type="submit"] {
padding: 8px 15px;
background-color: #008CBA;
color: white;
border: none;
cursor: pointer;
}

button[type="submit"]:hover {
background-color: #007B9A;
}

Advanced EJS Features

1. Custom Delimiters

You can customize EJS delimiters if you prefer:

javascript
app.set('view engine', 'ejs');
app.set('view options', {
delimiter: '?' // Change delimiters to <? ?>
});

2. Caching Templates

For production environments, enabling template caching can improve performance:

javascript
app.set('view cache', true); // Enable template caching

3. Client-side EJS

EJS can also be used in the browser:

html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/ejs.min.js"></script>
<script>
let template = '<h1><%= title %></h1>';
let data = { title: 'Hello from Client-side EJS!' };
document.getElementById('output').innerHTML = ejs.render(template, data);
</script>

Common Patterns and Best Practices

  1. Keep Logic Simple: Try to keep complex logic in your routes/controllers, not in your templates.

  2. Use Layouts: Create a main layout template and extend it for different pages.

  3. Organize Templates: Use folders to organize templates by feature or page type.

  4. Error Handling: Add error handling in your templates:

html
<% try { %>
<%= potentiallyProblematicFunction() %>
<% } catch (e) { %>
<p>An error occurred: <%= e.message %></p>
<% } %>
  1. Environment-Specific Content: Adapt content based on the environment:
html
<% if (process.env.NODE_ENV === 'development') { %>
<div class="dev-banner">Development Environment</div>
<% } %>

Summary

In this tutorial, we've covered:

  • Setting up Express with the EJS templating engine
  • Understanding EJS syntax and different tags
  • Working with data in EJS templates
  • Creating reusable template partials
  • Building a complete to-do application with EJS and Express
  • Advanced EJS features and best practices

EJS is a powerful yet simple templating engine that works well with Express. Its straightforward approach of embedding JavaScript directly into HTML makes it easy to learn and effective for building dynamic web applications.

Additional Resources

Exercises

  1. Add a "Delete Todo" feature to the to-do application.
  2. Create a "Filter" feature to show only completed or active to-dos.
  3. Implement a simple blog with posts and comments using EJS and Express.
  4. Create a user profile page that displays different information based on user roles.
  5. Build a dashboard with multiple components using EJS partials.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)