Skip to main content

Express HTML Responses

When building web applications with Express.js, one of the most common tasks is sending HTML responses to the client. Whether you're building a simple webpage or a complex web application, understanding how to effectively send HTML content is essential. In this guide, we'll explore various methods to send HTML responses in Express, from simple strings to complete web pages.

Introduction to HTML Responses

In Express, sending an HTML response allows your server to deliver rendered web pages to browsers. Unlike JSON responses which are primarily used for APIs, HTML responses are what users actually see when they visit your website. Express provides multiple ways to send HTML content:

  1. Sending HTML strings directly
  2. Sending HTML files
  3. Using template engines for dynamic HTML
  4. Streaming HTML content

Let's explore each of these approaches in detail.

Sending HTML Strings

The simplest way to send HTML content is by sending an HTML string directly using the res.send() method.

javascript
app.get('/', (req, res) => {
res.send('<h1>Hello World!</h1><p>Welcome to my Express server</p>');
});

This approach works well for simple responses but becomes unwieldy for larger HTML documents.

Setting Content Type

By default, Express will automatically set the Content-Type header to text/html when you send a string that begins with <. However, you can explicitly set the content type using res.type() or res.set():

javascript
app.get('/explicit-html', (req, res) => {
res.type('html');
// OR
// res.set('Content-Type', 'text/html');
res.send('<h1>This is HTML content</h1>');
});

Sending HTML Files

For larger HTML files, it's better to store them separately and send them using res.sendFile().

First, create an HTML file (e.g., index.html) in your project:

html
<!DOCTYPE html>
<html>
<head>
<title>My Express App</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
line-height: 1.6;
}
h1 {
color: #333;
}
</style>
</head>
<body>
<h1>Welcome to My Express Application</h1>
<p>This is a complete HTML file served by Express.</p>
</body>
</html>

Then, serve this file using Express:

javascript
const path = require('path');

app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});

The path.join() method ensures that the file path is correct across different operating systems.

Benefits of sending HTML files:

  1. Separates HTML content from your JavaScript code
  2. Makes your code more maintainable
  3. Allows for larger, more complex HTML documents
  4. Enables better collaboration between frontend and backend developers

Using a Public Directory for Static Files

For websites with multiple HTML files and assets (like CSS, images, and JavaScript files), Express provides a convenient way to serve static files:

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

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

// Start the server
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

With this setup, any files in the public directory will be served directly:

  • public/index.html will be available at http://localhost:3000/index.html or simply http://localhost:3000/
  • public/about.html will be available at http://localhost:3000/about.html
  • public/css/style.css will be available at http://localhost:3000/css/style.css

This approach is perfect for simple websites with static content.

Dynamic HTML with Template Engines

For most real-world applications, you'll want to generate HTML dynamically based on data. This is where template engines come in. Popular template engines for Express include EJS, Pug (formerly Jade), Handlebars, and Nunjucks.

Let's see an example using EJS (Embedded JavaScript):

First, install EJS:

bash
npm install ejs

Configure Express to use EJS:

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

// Set EJS as the view engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

Create a template file at views/home.ejs:

html
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= heading %></h1>
<p>Welcome, <%= username %>!</p>

<h2>Product List</h2>
<ul>
<% products.forEach(function(product) { %>
<li><%= product.name %> - $<%= product.price %></li>
<% }); %>
</ul>
</body>
</html>

Then, render the template with data:

javascript
app.get('/dynamic', (req, res) => {
res.render('home', {
title: 'Dynamic Express Page',
heading: 'Welcome to our Store',
username: 'John',
products: [
{ name: 'Laptop', price: 999.99 },
{ name: 'Smartphone', price: 699.99 },
{ name: 'Tablet', price: 349.99 }
]
});
});

When a user visits /dynamic, the server will:

  1. Read the home.ejs template
  2. Replace the variables (<%= title %>, etc.) with the provided data
  3. Send the resulting HTML to the client

Why Use Template Engines?

  • Separate presentation logic from business logic
  • Reuse layout components (headers, footers, navigation)
  • Conditionally display content based on data
  • Loop through data to generate repetitive elements
  • Insert dynamic data into HTML structure

Adding Headers and Status Codes

Sometimes you need to add specific headers or status codes to your HTML responses:

javascript
app.get('/custom-headers', (req, res) => {
res
.status(200)
.set({
'X-Custom-Header': 'Hello from Express',
'Cache-Control': 'public, max-age=300'
})
.send('<h1>HTML with custom headers</h1>');
});

For error pages:

javascript
app.get('/not-found', (req, res) => {
res
.status(404)
.send('<h1>404 - Page Not Found</h1><p>The page you are looking for doesn\'t exist.</p>');
});

Real-World Example: A Complete Express HTML Application

Let's build a simple but complete Express application that serves both static and dynamic HTML content:

javascript
const express = require('express');
const path = require('path');
const app = express();
const PORT = 3000;

// Set up EJS as the view engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

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

// Sample data (in a real app, this would come from a database)
const products = [
{ id: 1, name: 'Laptop', price: 999.99, inStock: true },
{ id: 2, name: 'Smartphone', price: 699.99, inStock: true },
{ id: 3, name: 'Headphones', price: 199.99, inStock: false },
{ id: 4, name: 'Tablet', price: 349.99, inStock: true }
];

// Home page route
app.get('/', (req, res) => {
res.render('index', {
title: 'Express Store',
message: 'Welcome to our online store!'
});
});

// Products page route
app.get('/products', (req, res) => {
res.render('products', {
title: 'Our Products',
products: products
});
});

// Single product page route
app.get('/products/:id', (req, res) => {
const product = products.find(p => p.id === parseInt(req.params.id));

if (!product) {
return res.status(404).render('error', {
title: 'Product Not Found',
message: 'The requested product does not exist.'
});
}

res.render('product-details', {
title: product.name,
product: product
});
});

// Custom 404 page for any undefined routes
app.use((req, res) => {
res.status(404).render('error', {
title: 'Page Not Found',
message: 'The page you are looking for does not exist.'
});
});

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

This example covers:

  • Setting up a template engine
  • Serving static files
  • Creating multiple routes with dynamic content
  • Handling errors with custom HTML pages
  • URL parameters for dynamic routes

Performance Considerations

When sending HTML responses, keep these performance factors in mind:

  1. Caching: Set appropriate cache headers for static HTML content.

    javascript
    app.get('/cached-page', (req, res) => {
    res.set('Cache-Control', 'public, max-age=300');
    res.sendFile(path.join(__dirname, 'public', 'cached-page.html'));
    });
  2. Compression: Use compression middleware to reduce payload size.

    javascript
    const compression = require('compression');
    app.use(compression());
  3. HTML Minification: Consider minifying HTML for production environments.

  4. Streaming Large Responses: For large HTML files, use streaming rather than loading the entire file into memory.

    javascript
    app.get('/large-file', (req, res) => {
    const filePath = path.join(__dirname, 'large-file.html');
    const stream = fs.createReadStream(filePath);
    stream.pipe(res);
    });

Summary

Express provides multiple ways to send HTML responses, each suited to different scenarios:

  1. HTML Strings: Best for simple, small responses.
  2. HTML Files: Ideal for static pages and separating concerns.
  3. Static File Directories: Perfect for websites with multiple HTML files and assets.
  4. Template Engines: Essential for dynamic content and reusable components.

By understanding these approaches, you can choose the most appropriate method for your specific use case and build more efficient and maintainable web applications.

Additional Resources

Exercises

  1. Create a simple Express server that serves an HTML file with CSS styling.
  2. Build a dynamic webpage using a template engine that displays a list of items from an array.
  3. Create a multi-page website with a shared header and footer using template partials.
  4. Build a 404 page handler that returns a custom HTML error page.
  5. Create an Express route that generates and returns an HTML table based on data from a JSON file.


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