Public Holidays Coupon Code Code Compiler

Build a simple SPA using Nodejs and Express


Sep 30, 2025

Build a simple SPA using Nodejs and Express

Single Page Applications (SPAs) have revolutionized modern web development by providing seamless user experiences without page reloads. In this comprehensive tutorial, you'll learn how to build a simple yet functional SPA using Node.js and Express.js. We'll cover everything from setting up your development environment to implementing client-side routing and API endpoints.

What is a Single Page Application?

A Single Page Application is a web application that loads a single HTML page and dynamically updates content as users interact with the app. Unlike traditional multi-page applications, SPAs don't require full page reloads, resulting in faster navigation and a more fluid user experience similar to desktop applications.

Prerequisites

Before we begin, make sure you have the following installed on your system:

  • Node.js (version 14 or higher)
  • npm (Node Package Manager)
  • A code editor (VS Code, Sublime Text, or your preferred editor)
  • Basic understanding of JavaScript, HTML, and CSS

Project Setup

1. Initialize Your Project

First, create a new directory for your project and initialize it with npm. Open your terminal and run the following commands:

// Navigate to your projects directory
cd your-projects-folder

// Create a new project directory
mkdir simple-spa-nodejs

// Navigate into the project directory
cd simple-spa-nodejs

// Initialize npm (creates package.json)
npm init -y

2. Install Dependencies

Install Express.js, which will serve as our backend framework:

// Install Express
npm install express

// Install nodemon as a dev dependency for auto-restarting the server
npm install --save-dev nodemon

Project Structure

Create the following folder structure for your project:

simple-spa-nodejs/
?
??? public/
?   ??? css/
?   ?   ??? style.css
?   ??? js/
?   ?   ??? app.js
?   ??? index.html
?
??? server.js
??? package.json
??? package-lock.json

Building the Backend

3. Create the Express Server

? Create file: server.js (in root directory)

Create a file named server.js in your project root directory. This will be our main server file:

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

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware to parse JSON
app.use(express.json());

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

// API Routes
app.get('/api/data', (req, res) => {
    const data = [
        { id: 1, title: 'Home', content: 'Welcome to our SPA!' },
        { id: 2, title: 'About', content: 'This is a simple SPA built with Node.js and Express.' },
        { id: 3, title: 'Contact', content: 'Get in touch with us!' }
    ];
    res.json(data);
});

// API endpoint for specific page data
app.get('/api/page/:id', (req, res) => {
    const pages = {
        '1': { id: 1, title: 'Home', content: 'Welcome to our Single Page Application! This app demonstrates how to build a modern SPA using Node.js and Express.' },
        '2': { id: 2, title: 'About', content: 'This is a simple Single Page Application built with Node.js and Express. It features client-side routing and dynamic content loading without page reloads.' },
        '3': { id: 3, title: 'Contact', content: 'Feel free to reach out to us at contact@example.com. We would love to hear from you!' }
    };
    
    const page = pages[req.params.id];
    if (page) {
        res.json(page);
    } else {
        res.status(404).json({ error: 'Page not found' });
    }
});

// Catch-all route to serve index.html for client-side routing
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// Start the server
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});
Note: The catch-all route (app.get('*')) is crucial for SPAs. It ensures that all routes are handled by the client-side router, allowing direct URL navigation to work correctly.

Building the Frontend

4. Create the HTML Structure

? Create file: public/index.html

Create an index.html file inside the public directory:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="A simple Single Page Application built with Node.js and Express">
    <title>Simple SPA - Node.js + Express</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>Simple SPA with Node.js</h1>
            <nav>
                <ul>
                    <li><a href="/" data-link>Home</a></li>
                    <li><a href="/about" data-link>About</a></li>
                    <li><a href="/contact" data-link>Contact</a></li>
                </ul>
            </nav>
        </header>
        
        <main id="content">
            <div class="loading">Loading...</div>
        </main>
        
        <footer>
            <p>© 2025 Simple SPA. Built with Node.js and Express.</p>
        </footer>
    </div>
    
    <script src="/js/app.js"></script>
</body>
</html>

5. Add Styling

? Create file: public/css/style.css

Create a style.css file inside the public/css directory:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    padding: 20px;
}

.container {
    max-width: 900px;
    margin: 0 auto;
    background-color: white;
    border-radius: 10px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
    overflow: hidden;
}

header {
    background-color: #667eea;
    color: white;
    padding: 30px;
}

header h1 {
    font-size: 2em;
    margin-bottom: 20px;
}

nav ul {
    list-style: none;
    display: flex;
    gap: 20px;
}

nav a {
    color: white;
    text-decoration: none;
    padding: 10px 20px;
    border-radius: 5px;
    transition: background-color 0.3s;
}

nav a:hover,
nav a.active {
    background-color: rgba(255, 255, 255, 0.2);
}

main {
    padding: 40px;
    min-height: 400px;
}

.loading {
    text-align: center;
    color: #667eea;
    font-size: 1.2em;
    padding: 50px;
}

.page-content h2 {
    color: #667eea;
    margin-bottom: 20px;
    font-size: 2em;
}

.page-content p {
    color: #555;
    font-size: 1.1em;
    line-height: 1.8;
}

footer {
    background-color: #f8f9fa;
    text-align: center;
    padding: 20px;
    color: #666;
    border-top: 1px solid #e0e0e0;
}

@media (max-width: 768px) {
    nav ul {
        flex-direction: column;
        gap: 10px;
    }
    
    main {
        padding: 20px;
    }
}

6. Implement Client-Side Routing

? Create file: public/js/app.js

Create an app.js file inside the public/js directory. This file will handle client-side routing and dynamic content loading:

// Router class for handling client-side routing
class Router {
    constructor() {
        this.routes = {
            '/': 1,
            '/about': 2,
            '/contact': 3
        };
        this.init();
    }
    
    init() {
        // Handle navigation clicks
        document.addEventListener('click', (e) => {
            if (e.target.matches('[data-link]')) {
                e.preventDefault();
                const url = e.target.getAttribute('href');
                this.navigate(url);
            }
        });
        
        // Handle browser back/forward buttons
        window.addEventListener('popstate', () => {
            this.loadContent();
        });
        
        // Load initial content
        this.loadContent();
    }
    
    navigate(url) {
        window.history.pushState(null, null, url);
        this.loadContent();
    }
    
    async loadContent() {
        const path = window.location.pathname;
        const pageId = this.routes[path] || 1;
        
        // Update active navigation link
        this.updateActiveLink(path);
        
        // Fetch and display content
        try {
            const response = await fetch(`/api/page/${pageId}`);
            if (!response.ok) {
                throw new Error('Page not found');
            }
            const data = await response.json();
            this.renderContent(data);
        } catch (error) {
            this.renderError(error.message);
        }
    }
    
    renderContent(data) {
        const content = document.getElementById('content');
        content.innerHTML = `
            <div class="page-content">
                <h2>${data.title}</h2>
                <p>${data.content}</p>
            </div>
        `;
    }
    
    renderError(message) {
        const content = document.getElementById('content');
        content.innerHTML = `
            <div class="page-content">
                <h2>Error</h2>
                <p>${message}</p>
            </div>
        `;
    }
    
    updateActiveLink(path) {
        // Remove active class from all links
        document.querySelectorAll('nav a').forEach(link => {
            link.classList.remove('active');
        });
        
        // Add active class to current link
        const activeLink = document.querySelector(`nav a[href="${path}"]`);
        if (activeLink) {
            activeLink.classList.add('active');
        }
    }
}

// Initialize the router when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
    new Router();
});

Running Your Application

7. Update Package.json Scripts

? Edit file: package.json (in root directory)

Add the following scripts to your package.json file:

{
  "name": "simple-spa-nodejs",
  "version": "1.0.0",
  "description": "A simple SPA built with Node.js and Express",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "keywords": ["spa", "nodejs", "express", "single-page-application"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

8. Start Your Server

Run the following command in your terminal to start the development server:

// For development (auto-restarts on file changes)
npm run dev

// For production
npm start

Open your browser and navigate to http://localhost:3000. You should see your Single Page Application running!

How It Works

Server-Side Routing

The Express server handles two types of routes:

  • API Routes: These routes (like /api/data and /api/page/:id) return JSON data to the client.
  • Catch-All Route: The app.get('*') route ensures that all non-API requests serve the main HTML file, allowing client-side routing to take over.

Client-Side Routing

The client-side JavaScript (app.js) implements a custom router that:

  • Intercepts link clicks and prevents default browser navigation
  • Uses the History API to update the URL without page reloads
  • Fetches content from the API based on the current route
  • Dynamically updates the page content without refreshing

Testing Your SPA

Test your application by:

  1. Clicking on different navigation links and observing that the page doesn't reload
  2. Using browser back/forward buttons to verify proper routing
  3. Directly accessing URLs like http://localhost:3000/about to ensure server-side routing works
  4. Opening the browser console to check for any errors

Enhancing Your SPA

Now that you have a basic SPA, consider these enhancements:

  • Database Integration: Connect to MongoDB or PostgreSQL to store and retrieve dynamic content
  • Authentication: Add user login functionality using JWT or sessions
  • Form Handling: Implement forms that submit data via AJAX without page reloads
  • Loading States: Add loading spinners and animations for better UX
  • Error Handling: Implement comprehensive error handling for failed API requests
  • SEO Optimization: Add server-side rendering or pre-rendering for better search engine visibility

Common Pitfalls to Avoid

  • Forgetting the Catch-All Route: Without it, direct URL access will fail
  • Not Handling Browser History: Always listen to the popstate event
  • Mixing Client and Server Routing: Keep API routes separate from static routes
  • Ignoring Loading States: Always show feedback during asynchronous operations

Conclusion

Congratulations! You've successfully built a simple Single Page Application using Node.js and Express. This foundation can be extended to create more complex applications with features like user authentication, real-time updates with WebSockets, and integration with modern frontend frameworks like React or Vue.js.

The key takeaway is understanding how client-side routing works in conjunction with server-side API endpoints to create a seamless, modern web experience. Keep experimenting and building upon this foundation to create amazing web applications!

Copyright 2025. All rights are reserved