Building Your First Multi-Page Website with a Node.js Backend
This guide will walk you through creating a simple, yet fully functional, multi-page website from scratch. You will build the frontend with HTML, CSS, and JavaScript, and power the contact form with a lightweight Node.js and Express backend.
What You'll Learn:
- How to structure a web project with a separate frontend and backend.
- How to create responsive HTML pages and style them with CSS.
- How to handle form submissions with client-side JavaScript using the fetch API.
- How to build a basic API with Node.js and Express to handle data.
- How to save and read data from a file on the server.
Part 1: Project Overview & File Structure
We will build a three-page website: a Home page, an About page, and a Contact page. The contact form will submit data to our Node.js server, which will save the messages into a JSON file.
Final Folder Structure
Visualizing the structure helps understand how the pieces connect. A clean separation between frontend (`public`) and backend (`server`) code is a professional standard that makes the project easier to manage.
my-website/
βββ public/ # Contains all frontend files served to the browser
β βββ index.html # Home page
β βββ about.html # About page
β βββ contact.html # Contact page
β βββ css/
β β βββ styles.css # All our styling rules
β βββ js/
β βββ script.js # Client-side JavaScript
βββ server/ # Contains our backend server logic
β βββ server.js # The main Express server file
β βββ messages.json # A simple file to store form submissions (created automatically)
βββ package.json # Node.js project configuration file
Part 2: Prerequisites
Before you begin, ensure you have the following tools installed. You can check if you have Node.js and npm installed by opening your terminal and running `node -v` and `npm -v`.
- Node.js: This is the JavaScript runtime for our backend. It includes npm (Node Package Manager), which we'll use to manage project dependencies. To Install: Download it from the official nodejs.org website.
- A Code Editor: A plain text editor designed for coding. Recommended: Visual Studio Code (free and powerful).
- A Terminal or Command Prompt: A command-line interface to navigate your computer and run commands. On Windows, use Command Prompt or PowerShell. On macOS or Linux, use the Terminal application.
Part 3: Setting Up the Project
Let's create the project folder and initialize it as a Node.js project. This setup process creates a `package.json` file, which acts as the "ID card" for our project, tracking its name, version, and the external libraries (dependencies) it needs to run.
Step 1: Create the Project Directory
Open your terminal and run these commands one by one to create a folder and navigate into it.
# Create a new folder named 'my-website'
mkdir my-website
# Navigate into the newly created folder
cd my-website
Step 2: Initialize the Node.js Project
Now, initialize a Node.js project using npm. This creates a `package.json` file to track our project's details and dependencies.
# The '-y' flag accepts all the default settings
npm init -y
Step 3: Install Required Packages
We need a few helper libraries (packages) for our server. Let's install them.
npm install express body-parser cors
- express: A fast, minimalist web framework for Node.js. It simplifies creating web servers and APIs, handling things like routing (figuring out what to do when a user visits a URL) and requests.
- body-parser: This is "middleware" that helps our server understand the data sent from the browser. When our contact form sends data as JSON, this package parses it and makes it easily accessible in our code.
- cors (Cross-Origin Resource Sharing): A security mechanism that browsers use. This middleware package makes it easy to tell the browser that our frontend (served from our server) is allowed to make requests to our backend API (also on the same server).
Part 4: Building the Frontend (HTML, CSS, & JS)
This is the part of your website that users will see and interact with in their browser. We will keep it clean and simple.
Step 1: Create the public Folder and Subfolders
This command creates the `public` directory along with its `css` and `js` subdirectories in one go.
mkdir -p public/css public/js
Step 2: Create the HTML Pages
Create the three HTML files inside the `public/` folder. Notice the consistent structure with `
public/index.html (Home Page)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My Website β Home</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<header class="site-header">
<div class="container">
<h1 class="brand">My Website</h1>
<nav>
<a href="index.html">Home</a>
<a href="about.html">About</a>
<a href="contact.html">Contact</a>
</nav>
</div>
</header>
<main class="container">
<section class="hero">
<h2>Welcome to My Website</h2>
<p>Simple, fast, and responsive β built from scratch.</p>
<a class="btn" href="contact.html">Get in touch</a>
</section>
</main>
<footer class="site-footer">
<div class="container">
<p>Β© <span id="year"></span> My Website</p>
</div>
</footer>
<script src="js/script.js"></script>
</body>
</html>
public/contact.html (Contact Page with Form)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Contact β My Website</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<header class="site-header">
<!-- ... same header as index.html ... -->
</header>
<main class="container">
<h2>Contact Us</h2>
<form id="contactForm">
<label>Name <input type="text" name="name" required /></label>
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" rows="5" required></textarea></label>
<button type="submit">Send</button>
</form>
<div id="status" aria-live="polite"></div>
</main>
<footer class="site-footer">
<!-- ... same footer as index.html ... -->
</footer>
<script src="js/script.js"></script>
</body>
</html>
Step 3: Add CSS for Styling
Create the file `public/css/styles.css`. This CSS provides a clean, modern, and responsive design. The `box-sizing: border-box` rule is a modern standard that makes layout math more intuitive. The `@media` query at the end ensures the header looks good on smaller mobile screens.
/* public/css/styles.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; line-height: 1.5; color: #111; background: #f7f7f9; }
.container { max-width: 1000px; margin: 0 auto; padding: 1rem; }
.site-header { background: #0b74de; color: #fff; padding: 1rem 0; }
.site-header .container { display: flex; align-items: center; justify-content: space-between; }
nav a { color: #fff; margin-left: 0.75rem; text-decoration: none; }
form label { display: block; margin-bottom: 0.75rem; }
input, textarea { width: 100%; padding: 0.6rem; margin-top: 0.25rem; border: 1px solid #ddd; }
button { padding: 0.6rem 1rem; border: none; background: #0b74de; color: #fff; cursor: pointer; }
.site-footer { margin-top: 2rem; padding: 1rem 0; text-align: center; color: #666; }
@media (max-width: 640px) { .site-header .container { flex-direction: column; align-items: flex-start; } }
Step 4: Add Client-Side JavaScript
Create the file `public/js/script.js`. This script waits for the page to fully load (`DOMContentLoaded`), then it finds the contact form. When the user submits the form, our script steps in to prevent the page from reloading (`e.preventDefault()`), grabs the data, and sends it to our backend API using the modern `fetch` function. This provides a smooth user experience.
// public/js/script.js
document.addEventListener('DOMContentLoaded', () => {
const yearEl = document.getElementById('year');
if (yearEl) { yearEl.textContent = new Date().getFullYear(); }
const form = document.getElementById('contactForm');
const status = document.getElementById('status');
if (form) {
form.addEventListener('submit', async (e) => {
e.preventDefault();
status.textContent = 'Sending...';
const formData = new FormData(form);
const payload = Object.fromEntries(formData.entries());
try {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
status.textContent = 'Message sent β thank you!';
form.reset();
} else {
status.textContent = 'Error: Unable to send message.';
}
} catch (err) {
status.textContent = 'A network error occurred.';
}
});
}
});
Part 5: Building the Backend (Node.js & Express)
Now, we'll create the server that will serve our frontend files and handle the contact form API endpoint.
Step 1: Create the Server Directory and File
mkdir server
Step 2: Write the Server Code
Add the following code to `server/server.js`. The comments explain what each block of code does, from loading our packages to serving the static files, and finally, defining the API endpoint that will listen for our form submissions.
// server/server.js
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const cors = require('cors');
const app = express();
const PORT = 3000;
const DATA_FILE = path.join(__dirname, 'messages.json');
// --- Middleware ---
app.use(cors());
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, '..', 'public')));
// --- File System Setup ---
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify([]));
}
// --- API Endpoints ---
app.post('/api/contact', (req, res) => {
const { name, email, message } = req.body || {};
if (!name || !email || !message) {
return res.status(400).json({ error: 'Name, email, and message are required.' });
}
const newMessage = { id: Date.now(), name, email, message, createdAt: new Date().toISOString() };
try {
const messages = JSON.parse(fs.readFileSync(DATA_FILE));
messages.push(newMessage);
fs.writeFileSync(DATA_FILE, JSON.stringify(messages, null, 2));
res.status(201).json({ success: true, message: 'Message saved successfully.' });
} catch (err) {
res.status(500).json({ error: 'Internal server error.' });
}
});
// --- Start the Server ---
app.listen(PORT, () => {
console.log(`Server is running and accessible at http://localhost:${PORT}`);
});
Step 3: Add a Start Script to package.json
Open `package.json` and modify the `"scripts"` section. This creates a shortcut so we can run our server by simply typing `npm start` instead of the longer `node server/server.js` command.
"scripts": {
"start": "node server/server.js"
}
Part 6: Running Your Website Locally
You have now built both the frontend and the backend. Let's run it!
Make sure you are in the root directory of your project (`my-website`) in your terminal, then run the start command:
npm start
You should see the message: `Server is running and accessible at http://localhost:3000`. Open your web browser and navigate to that address. You should see your website's home page. Click the navigation links to visit the other pages, and test the contact form. A `messages.json` file should appear in your `server` folder with your test submission!
Part 7: Next Steps and Best Practices
Congratulations on building a full-stack website! Here is how you can improve and expand upon it.
- Use a Real Database: Storing data in a JSON file is great for learning, but it isn't efficient for many users. The next step is to use a database like SQLite (simple, file-based) or MongoDB (more powerful).
- Send Email Notifications: Instead of just saving the message, use a library like `nodemailer` to send an email to yourself whenever someone submits the form.
- Add Server-Side Validation: Add more robust checks on the server to ensure the email is a valid format and the message isn't too long before saving it.
- Use a Templating Engine: To avoid duplicating the header and footer HTML on every page, use a templating engine like EJS or Pug to create reusable layout components.
- Deploy Your Site: To share your site with the world, deploy it! Services like Render or Railway make it easy to deploy full-stack Node.js applications for free.