WDX-180
Web Development X
Express + EJS Fundamentals
Building Your First Server-Rendered Web Application
“A web framework is not magic. It is simply a collection of abstractions that make common tasks easier.”
Today, we’ll pull back the curtain and understand what Express and EJS are actually doing.
Learning Objectives
By the end of this lesson, students will be able to:
- Explain the difference between a web server and a web application.
- Explain what Express.js does and why it exists.
- Create and configure an Express application.
- Understand HTTP requests and responses.
- Serve static files using Express middleware.
- Configure EJS as a template engine.
- Render dynamic HTML using data from JavaScript.
- Create reusable layouts and partials.
- Understand the purpose of middleware.
- Build a small server-rendered application from scratch.
Before We Begin
What Are We Building?
Throughout this course we will gradually build a Product CMS.
A CMS (Content Management System) allows users to manage data through a web interface.
Examples:
- Shopify → Products
- WordPress → Blog Posts
- YouTube Studio → Videos
- Netflix Admin Dashboard → Movies & Shows
Our CMS will allow users to:
- View products
- Create products
- Edit products
- Delete products
This is known as CRUD:
| Operation | Meaning |
|---|---|
| Create | Add data |
| Read | View data |
| Update | Modify data |
| Delete | Remove data |
Today we are building the foundation.
No database yet.
No authentication yet.
Just Express, EJS, and understanding how web applications work.
What Is Express?
The Problem
Node.js can already create web servers.
Example:
const http = require('http');
http.createServer((req, res) => {
res.end('Hello World');
}).listen(3000);
But building real applications this way becomes painful.
We would need to manually:
- Parse URLs
- Handle routes
- Parse forms
- Serve files
- Handle errors
Express solves these problems.
What Express Gives Us
Express provides:
Routing
app.get('/', () => {});
app.post('/products', () => {});
Middleware
app.use(...)
Request Helpers
req.body
req.params
req.query
Response Helpers
res.render(...)
res.json(...)
res.redirect(...)
Express is essentially a thin layer on top of Node’s HTTP server.
Project Setup
Create Project Folder
mkdir express-crud
cd express-crud
Initialize Node Project
npm init -y
This creates a package.json that looks like this:
{
"name": "express-crud",
"version": "1.0.0",
...
}
Install Dependencies
npm install express ejs express-ejs-layouts
What Did We Install?
| Package | Purpose |
|---|---|
| express | Web framework |
| ejs | Template engine |
| express-ejs-layouts | Layout support |
Add Scripts
{
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js"
}
}
Now:
npm run dev
automatically reloads the server whenever files change.
Your future self will thank you.
Security Considerations
Before installing any dependencies, it’s important to run some safety checks and ensure that you are not installing an infected or malicious npm module.
Here are some steps to protect you:
-
Install and
npqto install npm modules instead ofnpm. Just runnpq installinstead ofnpm install. Installation instructions can be found here. -
Go to socket.dev and use their
NPM Search Packagesfield to check whether a particular npm module and version is safe to install. -
Always copy and paste the
npm installcommand from the official documentation and ensure that you have correctly copied the full line. Typosquatting happens when you’ve accidentally typednpm install expresinstead ofnpm install express
Version Control
Of course, every serious projects needs some kind of version control.
Before you start this project make sure to initialize the project folder as a git repository by running git init.
Now, every time you complete a step of this module or implement a feature, make sure to stage (git add) and commit (git commit) your changes.
Project Structure
Professional projects rely on predictable structure.
express-crud/
├── index.js
├── package.json
├── views/
│ ├── layout.ejs
│ ├── index.ejs
│ └── partials/
│ ├── header.ejs
│ └── footer.ejs
├── public/
│ └── css/
│ └── style.css
Creating Our First Express Server
Create:
// index.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello Express!');
});
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
Run:
npm run dev
Visit:
http://localhost:3000
Congratulations.
You now have a working web server.
You’re officially serving bytes to strangers.
Or at least to yourself.
Understanding Routes
A route defines:
HTTP METHOD + URL
Examples:
| Method | URL | Purpose |
|---|---|---|
| GET | / | Homepage |
| GET | /products | List products |
| GET | /products/1 | View product |
| POST | /products | Create product |
Example:
app.get('/about', (req, res) => {
res.send('About Page');
});
Now:
localhost:3000/about
returns:
About Page
Middleware
Middleware is one of the most important concepts in Express.
A middleware function sits between:
Request
↓
Middleware
↓
Route
↓
Response
Example:
app.use((req, res, next) => {
console.log(req.method, req.url);
next();
});
Every request now gets logged.
Why Middleware Exists
Middleware can:
- Log requests
- Parse forms
- Parse JSON
- Authenticate users
- Validate data
- Handle errors
Most Express applications are simply chains of middleware.
Serving Static Files
Websites usually contain:
- CSS
- Images
- JavaScript
These are called static assets.
Create:
app.use(express.static('public'));
Now:
public/css/style.css
becomes accessible through:
/css/style.css
Create:
body {
font-family: sans-serif;
}
Introducing EJS
So far we have been sending strings:
res.send('Hello');
Real applications send HTML.
Instead of writing HTML inside JavaScript, we use templates.
This is where EJS comes in.
What Is EJS?
EJS means:
Embedded JavaScript
It allows JavaScript inside HTML.
Example:
<h1><%= title %></h1>
If:
title = 'Products'
Output becomes:
<h1>Products</h1>
Configuring EJS
index.js:
const path = require('node:path');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
Create:
app.get('/', (req, res) => {
res.render('index', {
title: 'Products CMS'
});
});
Create:
<!-- views/index.ejs -->
<h1><%= title %></h1>
The page now renders dynamically.
Passing Data to Views
Let’s pass an array.
const products = [
{ name: 'Keyboard' },
{ name: 'Mouse' },
{ name: 'Monitor' }
];
app.get('/', (req, res) => {
res.render('index', {
title: 'Products',
products
});
});
View:
<h1><%= title %></h1>
<ul>
<% products.forEach(product => { %>
<li><%= product.name %></li>
<% }) %>
</ul>
Output:
Keyboard
Mouse
Monitor
EJS Syntax
Output Escaped Data
<%= name %>
Recommended for user data.
Protects against XSS attacks.
Execute JavaScript
<% products.forEach(...) %>
No output.
Only logic.
Output Raw HTML
<%- html %>
Dangerous unless trusted.
Avoid when possible.
Layouts
Imagine having:
<header>...</header>
<footer>...</footer>
<nav>...</nav>
copied into 30 pages.
That becomes a maintenance nightmare.
Layouts solve this.
Enable Layouts
index.js:
const expressLayouts = require('express-ejs-layouts');
app.use(expressLayouts);
layout.ejs
<!doctype html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<%- include('partials/header') %>
<main>
<%- body %>
</main>
<%- include('partials/footer') %>
</body>
</html>
Partials
Partials are reusable pieces of UI.
Header Section
views/partials/header.ejs:
<header>
<h1>Products CMS</h1>
</header>
Footer Section
views/partials/footer.ejs:
<footer>
<p>Copyright 2026</p>
</footer>
Benefits:
- Reusable
- Easier maintenance
- Consistent UI
This follows the DRY principle:
Don’t Repeat Yourself
Building a Small Product List
Route
const products = [
{
id: 1,
name: 'Keyboard',
price: 49.99
},
{
id: 2,
name: 'Mouse',
price: 19.99
}
];
app.get('/', (req, res) => {
res.render('index', {
title: 'Products',
products
});
});
View
<h2>Products</h2>
<ul>
<% products.forEach(product => { %>
<li>
<strong><%= product.name %></strong>
-
$<%= product.price.toFixed(2) %>
</li>
<% }) %>
</ul>
Common Beginner Mistakes
1. Forgetting to Install Dependencies
npm install express ejs
2. Wrong Views Folder
app.set('views', ...)
must point to the correct directory.
3. Forgetting Middleware
app.use(express.urlencoded({ extended: true }));
Without it:
req.body
will be empty.
4. CSS Not Loading
Ensure:
app.use(express.static('public'));
exists.
5. Using <%- %> Everywhere
Never trust user input.
Use:
<%= value %>
unless you specifically need raw HTML.
Key Takeaways
Today you learned:
- How the Request → Response cycle works
- What Express is and why it exists
- How routing works
- What middleware does
- How static files are served
- How EJS templates work
- How to pass data from JavaScript into HTML
- How layouts and partials reduce duplication
Assignment
Level 1
Create a page that displays:
Your Name
Your Favourite Programming Language
Your Years of Experience
using EJS variables.
Level 2
Create an array of 5 products.
Render them dynamically using a loop.
Level 3
Add a navigation bar partial.
Include:
- Home
- Products
- About
Bonus Challenge
Create:
GET /about
Render an EJS page explaining:
- What Express is
- What EJS is
- Why server-side rendering matters
⚠️ A large part of the content of this module was created using Generative AI (ChatGPT). The synthetic (AI-generated) content was reviewed and curated by Kostas Minaidis.