---
title: "Building Single-Page Applications (SPAs) with Knockout.js and RESTful APIs"  
description: "Single-page applications (SPAs) provide a seamless user experience by dynamically updating the content of a web page without requiring a full page rel"  
author: "Ravi Vishwakarma"  
published: 2024-06-26  
updated: 2024-06-26  
canonical: https://www.mindstick.com/blog/304436/building-single-page-applications-spas-with-knockout-js-and-restful-apis  
category: "knockcout js"  
tags: ["web development", "knockout.js", "front-end development"]  
reading_time: 5 minutes  

---

# Building Single-Page Applications (SPAs) with Knockout.js and RESTful APIs

[**Single-page applications (SPAs)**](https://www.mindstick.com/articles/334364/handling-single-page-applications-spas-insights-from-google-and-beyond) provide a seamless [user experience](https://www.mindstick.com/articles/12731/the-importance-of-feedback-to-the-user-experience) by dynamically updating the content of a web page without requiring a full page reload. Knockout.js, with its powerful data-binding and MVVM (Model-View-ViewModel) [architecture](https://www.mindstick.com/articles/249193/top-5-reasons-to-pursue-architecture), is an excellent choice for building SPAs. Combined with [RESTful APIs](https://www.mindstick.com/articles/337404/how-to-build-and-consume-restful-apis-using-php), you can create highly [responsive](https://www.mindstick.com/forum/12845/how-to-responsive-div-table-struggle-multiple-rows) and [interactive web](https://answers.mindstick.com/qa/104427/how-to-use-javascript-for-interactive-web-pages) applications. This guide will walk you through building an SPA using Knockout.js and RESTful APIs.

#### Setting Up the Project

**Create the Project [Structure](https://www.mindstick.com/articles/23258/choose-your-business-structure-wisely)**

```plaintext
knockout-spa/
├── index.html
├── app.js
├── styles.css
├── api/
│   ├── products.json
└── server.js
```

## Create the HTML File

## index.html

```html
<!doctype html>
<html lang="en">

<head>
    <!-- Meta tags for character set and viewport configuration -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Title of the webpage -->
    <title>Bootstrap demo</title>

    <!-- Link to Bootstrap CSS for styling -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

    <!-- Link to Knockout.js library for MVVM pattern support -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"
        integrity="sha512-vs7+jbztHoMto5Yd/yinM4/y2DOkPLt0fATcN+j+G4ANY2z4faIzZIOMkpBmWdcxt+596FemCh9M18NUJTZwvw=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <!-- Link to jQuery library -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>

<body>
    <!-- Link to external JavaScript file -->
    <script src="app.js"></script>
</body>

</html>
```

## Create the Knockout.js ViewModel

## app.js

```javascript
// ViewModel for the application
class AppViewModel {
    constructor() {
        var self = this;

        // Observables to store data and state
        self.response = ko.observable({}); // Observable to store the response data
        self.currentPage = ko.observable(0); // Observable to track the current page
        self.totalPages = ko.observable(0); // Observable to track the total number of pages
        self.pagination = ko.observable({}); // Observable to store pagination data

        self.isLoading = ko.observable(false); // Observable to track loading state
        self.error = ko.observable(null); // Observable to store error message

        self.pageNo = ko.observable(self.pagination().current_page ? self.pagination().current_page : 1);

        self.pageNo.subscribe(function(newValue){
            let url = 'https://api.artic.edu/api/v1/artworks?page='+ parseInt(newValue);
            self.fetchData(url);
        });

        // Function to fetch data from the API
        self.fetchData = async function (url = '') {
            self.isLoading(true); // Set loading state to true
            self.error(''); // Clear any previous errors
            let _url = url || 'https://api.artic.edu/api/v1/artworks'; // Default URL
            try {
                // Simulate an asynchronous API call using fetch
                let response = await fetch(_url);
                if (!response.ok) {
                    // If response is not ok, throw an error
                    throw new Error('Network response was not ok ' + response.statusText);
                }
                let result = await response.json(); // Parse response as JSON
                self.response(result); // Update the observable array with the fetched data

                // Extract pagination data from the response and update observables accordingly
                if (result.pagination) {
                    self.pagination(result.pagination);
                }
            } catch (err) {
                // If an error occurs during fetch, set error message
                self.error(err.message);
            } finally {
                // Set loading state to false regardless of success or failure
                self.isLoading(false);
            }
        };

        self.showDetails = function(link) {
            // Add your code to handle details here
            console.log("Link clicked:", link);
        }
    }
}

// Apply bindings
ko.applyBindings(new AppViewModel());
```

## Create the Product Section Template

Modify **index.html** to include the product list template.

```html
    <!-- Main container for the content -->
    <div class="container my-3">
        <!-- Header for the section -->
        <h2>Data from Server async</h2>
        <!-- Button to fetch data -->
        <button data-bind="click: fetchData('')" class="btn btn-secondary">Fetch Data</button>
        <hr />
        <div id="app">
            <!-- Display loading state -->
            <div data-bind="visible: isLoading">
                <div class="d-flex justify-content-center fw-semibold fs-5 align-items-center">
                    <span class="me-2">Loading...</span>
                    <div class="spinner-border" role="status">
                        <span class="visually-hidden">Loading...</span>
                    </div>
                </div>
            </div>

            <!-- Display error message if any -->
            <div class="d-flex justify-content-center fw-semibold fs-5 align-items-center"
                data-bind="visible: error, text: error" style="color: red;"></div>
            <div class="my-3 d-flex justify-content-end align-items-baseline">
                <form>
                    <div class="mx-3">
                        <input class="form-control" data-bind="value: $root.pageNo, valueUpdate: 'input'"
                            placeholder="page no" />
                    </div>
                </form>

                <!-- Pagination -->
                <span class="fw-semibold me-3" data-bind="with: { pages : pagination()}">
                    <span data-bind="text: pages.current_page"></span>
                    <span class="mx-1">of</span>
                    <span class="" data-bind="text: pages.total_pages"></span>
                </span>

                <ul class="pagination "
                    data-bind="with: { pages : pagination(), prevUrl: pagination().prev_url, nextUrl: pagination().next_url, currentPage: pagination().current_page ? pagination().current_page : 1 }">
                    <!-- First Page -->
                    <li class="page-item" data-bind="css: { 'disabled': !(currentPage > 1) }">
                        <a class="page-link" href="#" data-bind="click: function() { $root.fetchData(''); }">
                            First
                        </a>
                    </li>
                    <!-- Previous Page -->
                    <li class="page-item" data-bind="css: { 'disabled': !(currentPage > 1) }">
                        <a class="page-link" href="#" data-bind="click: function() { $root.fetchData(prevUrl); },
                                      enable: (currentPage > 1)">
                            Previous
                        </a>
                    </li>
                    <!-- Current Page -->
                    <li class="page-item active"><a class="page-link" href="#"
                            data-bind="text: currentPage ? currentPage : 1"></a></li>
                    <!-- Next Page -->
                    <li class="page-item" data-bind="css: { 'disabled': !(currentPage < pages.total_pages) }">
                        <a class="page-link" href="#" data-bind="click: function() { $root.fetchData(nextUrl); }">
                            Next
                        </a>
                    </li>
                    <!-- Last Page -->
                    <li class="page-item" data-bind="css: { 'disabled': !(currentPage < pages.total_pages) }">
                        <a class="page-link" href="#"
                            data-bind="click: function() { $root.fetchData('https://api.artic.edu/api/v1/artworks?page='+pages.total_pages); }">
                            Last
                        </a>
                    </li>
                </ul>

            </div>
            <div class="" data-bind="if: (response() && response().data && response().data.length <= 0)">
                <hr />
                <div class="d-flex justify-content-center fw-bold">
                    <div class="text-center">
                        <p class=" fs-2 ">
                            <span class="me-2">😕</span>
                            No records were found
                        </p>
                        <!-- <p class="fw-normal">try somthing new keywords</p> -->
                    </div>
                </div>
            </div>

            <!-- Display data if available -->
            <div class="row g-3 row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4"
                data-bind="visible: !isLoading(), foreach: response().data">
                <!-- Column for each item -->
                <div class="col">
                    <!-- Card to display item details -->
                    <div class="card h-100">

                        <!-- Item Image -->
                        <img data-bind="if: (image_id && thumbnail), attr: {
                            src: 'https://www.artic.edu/iiif/2/' + image_id + '/full/400,/0/default.jpg',
                            alt: thumbnail ? thumbnail.alt_text : ''
                        }" class="card-img-top" height="250">
                        <div class="card-body">
                            <!-- Card title bound to item's title -->
                            <h5 class="card-title text-capitalize" data-bind="text: title"></h5>
                            <!-- Card text bound to item's body -->
                            <!-- <p class="card-text" data-bind="text: body"></p> -->
                        </div>
                        <div class="card-footer border-0 pt-0">
                            <!-- Footer with item id and a link -->
                            <p class="m-0 d-flex justify-content-between">
                                <!-- Item id displayed -->
                                <span class="fs-5 fw-bold" data-bind="text: id"></span>
                                <!-- Link to comments page for the item -->
                                <a class=""
                                    data-bind="attr: { 'data-link': api_link }, click: function() { $root.showDetails(api_link); }">
                                    <!-- SVG icon inside the link -->
                                    <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor"
                                        class="bi bi-arrow-right-circle-fill" viewBox="0 0 16 16">
                                        <path
                                            d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0M4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5z" />
                                    </svg>
                                </a>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
```

Read more

[**Latest from knockout.js**](https://www.mindstick.com/category/article/knockcout-js)

[**CRUD operation in KnockoutJS.**](https://www.mindstick.com/articles/335888/crud-operation-in-knockoutjs)

[**Knockout.js: Building Dynamic Web Applications**](https://training.mindstick.com/courses/203/knockout-js-javascript-mvvc-framework-upcoming12)

---

Original Source: https://www.mindstick.com/blog/304436/building-single-page-applications-spas-with-knockout-js-and-restful-apis

Copyright © MindStick Software Pvt. Ltd. This Markdown version is provided for developers, AI systems, and offline reading.
