---
title: "CRUD operation in knockoutjs using AJAX."  
description: "Creating a CRUD (Create, Read, Update, Delete) application using KnockoutJS with AJAX involves several steps."  
author: "Ravi Vishwakarma"  
published: 2024-06-27  
updated: 2024-07-04  
canonical: https://www.mindstick.com/articles/336272/crud-operation-in-knockoutjs-using-ajax  
category: "knockcout js"  
tags: ["web development", "knockout.js", "knockout mvc", "front-end development"]  
reading_time: 8 minutes  

---

# CRUD operation in knockoutjs using AJAX.

Creating a CRUD (Create, Read, Update, Delete) application using KnockoutJS with AJAX involves several steps. Below is a detailed guide to help you through the process:

#### 1. Set Up Your Project

First, set up [your project](https://answers.mindstick.com/qa/93843/is-it-necessary-to-add-bootstrap-js-and-bootstrap-css-both-in-your-project) by including the necessary libraries and creating the basic HTML structure.

## HTML Structure

```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>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.min.js"
        integrity="sha512-b99MDNv5TqiZtPKH2UeHzDAVydmgrOJEPtaPPEF8AgV86eYyqINFI/K7/7f0+R4WNTAVv8KvvpjwfOYHv5rd5g=="
        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>
    <style type="text/css">
        .waitMe {
            z-index: 99999;
        }
    </style>
</head>

<body>
    <div class="position-absolute top-0 end-0 bottom-0 start-0 text-center bg-dark bg-opacity-10 waitMe d-none">
        <div class="d-flex justify-content-center align-content-center align-items-center h-100">
            <span class="mx-3 fw-bold">Loading . . .</span>
            <div class="spinner-border" role="status">
                <span class="visually-hidden">Loading...</span>
            </div>
        </div>
    </div>
    <!-- Main container for the content -->
    <div class="container my-3">
        <!-- Header for the section -->
        <h2>Data from Server</h2>

        <div class="d-flex w-100 justify-content-between ">
            <!-- Button trigger modal -->
            <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#postStaticBackdrop">
                New Post
            </button>
            <div>
                <input type="search" data-bind="value: searchText, valueUpdate: 'input'" class="form-control"
                    placeholder="Search ..." />
            </div>
        </div>
        <hr />
        <p class="fw-semibold text-black-50 text-end">
            <span class="" data-bind="text: filteredItemsLength"></span>
            <span>data found.</span>
        </p>
        <div data-bind="css: { 'd-none': filteredItemsLength() !== 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>
        <!-- Responsive row to display items fetched from the server -->
        <div class="row g-3 row-cols-1 row-cols-md-2 row-cols-lg-4 row-cols-xl-4" data-bind="foreach: filteredItems">
            <!-- Column for each item -->
            <div class="col">
                <!-- Card to display item details -->
                <div class="card h-100">
                    <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 -->
                        <div class="m-0 d-flex justify-content-between">
                            <!-- Item id displayed -->
                            <span class="fs-5 fw-bold">
                                <span data-bind="text: $index"></span>
                                <span class="text-black-50">
                                    (<small class="fs-6 fw-bold" data-bind="text: id"></small>)
                                </span>
                            </span>
                            <div class="">
                                <button class="btn btn-link text-primary pe-0"
                                    data-bind="click: $parent.editPost">Edit</button>
                                <button class="btn btn-link text-danger pe-0"
                                    data-bind="click: $parent.deletePost">Delete</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
<!-- Modal -->
<div class="modal fade" id="postStaticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
    aria-labelledby="postStaticBackdropLabel" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
        <div class="modal-content">
            <div class="modal-header">
                <h1 class="modal-title fs-5" id="postStaticBackdropLabel">Add New Post</h1>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <form class="row g-3"
                    data-bind="submit: function() { addPost({ title: newTitle(), body: newBody(), userId: userId(), id: id() }); }">
                    <input type="hidden" data-bind="value: id" />
                    <input type="hidden" data-bind="value: userId" />
                    <div class="col-12">
                        <label class="form-label fw-bold">Title</label>
                        <input type="text" placeholder="Title" id="newTitle" name="newTitle"
                            data-bind="value: newTitle, valueUpdate: 'input'" class="form-control">
                    </div>
                    <div class="col-12">
                        <label class="form-label fw-bold">Body</label>
                        <textarea rows="5" placeholder="Body" id="newBody" name="newBody"
                            data-bind="value: newBody, valueUpdate: 'input'" class="form-control"></textarea>
                    </div>
                    <div class="col-12">
                        <button type="submit" class="btn btn-primary px-5 rounded-pill">Save</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

<!-- Link to external JavaScript file -->
<script src="CRUD-with-js.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
    crossorigin="anonymous"></script>

</html>
```

#### 2. Create the ViewModel

Create a JavaScript file (e.g., `CRUD-with-js.js`) and define your ViewModel.

## ViewModel Definition

```javascript
var loader = $('.waitMe');

function toggleLoader(){
    if(loader.hasClass('d-none')){
        loader.removeClass('d-none');
    }else{
        loader.addClass('d-none');
    }
}

// app.js
// Initialize Knockout validation
ko.validation.init({
    registerExtenders: true, // Register custom validation rules
    messagesOnModified: true, // Show validation messages as soon as a field is modified
    insertMessages: true, // Insert validation messages next to the input elements
    parseInputAttributes: true, // Parse HTML5 input attributes for validation rules
    errorClass: 'text-danger fw-semibold', // CSS class for validation error messages
    messageTemplate: null // Use default message template
}, true);

class AppViewModel {
    constructor() {
        var self = this;

        // Observable array to hold the data fetched from the server
        self.items = ko.observableArray([]);
        self.searchText = ko.observable('');
        self.newTitle = ko.observable('').extend({
            required: {
                message: "Title is required."
            },
            minLength: {
                params: 2,
                message: "Title must be at least 2 characters."
            }
        });
        self.newBody = ko.observable('').extend({
            required: {
                message: "Body is required."
            },
            minLength: {
                params: 2,
                message: "Body must be at least 2 characters."
            }
        });

        self.userId = ko.observable('11');
        self.id = ko.observable(0);
        // Function to fetch data from the API
        self.fetchData = function() {
            $.ajax({
                url: 'https://jsonplaceholder.typicode.com/posts',
                beforeSend: function(xhr) {
                    toggleLoader();
                },
                success: function(result, status, xhr) {
                    if (status === 'success') {
                        self.items(result); // Corrected from 'item' to 'post'
                    }
                },
                error: function(xhr) {
                    alert("An error occurred: " + xhr.status + " " + xhr.statusText);
                },
                complete: function(xhr,status){
                    toggleLoader();
                }
            });
        };

        // Function to add a new post
        self.addPost = function(newPost) {
            // Check if the form is valid
            if (self.errors().length === 0) {
                // Determine if it's an update or a new post
                var isUpdate = newPost.id !== 0;

                // Construct the appropriate URL based on the action
                var url = isUpdate ? `https://jsonplaceholder.typicode.com/posts/${newPost.id}` : 'https://jsonplaceholder.typicode.com/posts';

                $.ajax({
                    url: url,
                    type: isUpdate ? 'PUT' : 'POST',
                    data: JSON.stringify(newPost),
                    headers: {
                        'Content-type': 'application/json; charset=UTF-8',
                    },
                    beforeSend: function(xhr) {
                        toggleLoader();
                    },
                    success: function(result, status, xhr) {
                        if (status === 'success') {
                            // Update the items array with the new or updated post
                            if (isUpdate) {
                                // Find the index of the existing post and update it
                                let index = self.items().findIndex(item => item.id === newPost.id);
                                if (index !== -1) {
                                    self.items.splice(index, 1, newPost);
                                }
                            } else {
                                self.items.push(newPost);
                            }

                            // Clear input fields after successful addition or update
                            self.newTitle('');
                            self.newBody('');
                            self.id(0);

                            // Hide modal using jQuery
                            $('#postStaticBackdrop').modal('hide');

                            // Hide all validation messages
                            self.errors.showAllMessages(false);
                        }
                    },
                    error: function(xhr) {
                        alert("An error occurred: " + xhr.status + " " + xhr.statusText);
                    },
                    complete: function(xhr,status){
                        toggleLoader();
                    }
                });

            } else {
                // Show validation errors
                self.errors.showAllMessages(true);
            }
        };

        // Function to delete a product from the list
        self.deletePost = function(post) {
            if (window.confirm("Are you sure you want to delete this product?")) {
                $.ajax({
                    url: `https://jsonplaceholder.typicode.com/posts/${post.id}`,
                    type: 'DELETE',
                    headers: {
                        'Content-type': 'application/json; charset=UTF-8',
                    },
                    beforeSend: function(xhr) {
                        toggleLoader();
                    },
                    success: function(result, status, xhr) {
                        if (status === 'success') {
                            self.items.remove(post); // Corrected from 'item' to 'post'
                        }
                    },
                    error: function(xhr) {
                        alert("An error occurred: " + xhr.status + " " + xhr.statusText);
                    },
                    complete: function(xhr,status){
                        toggleLoader();
                    }
                });
            }
        };

        // Function to start editing a product
        self.editPost = function(post) {
            self.newTitle(post.title); // Clear input after successful addition
            self.newBody(post.body); // Clear input after successful addition
            self.id(post.id);
            self.userId(post.userId);
            $('#postStaticBackdrop').modal('show');
        };

        // Computed observable to filter the items based on the filter criteria
        self.filteredItems = ko.computed(function() {
            //console.log(self.items().reverse());
            // Reverse the items array
            var reversedItems = self.items().slice().reverse();

            //var filter = self.filter().toLowerCase(); // Convert filter criteria to lowercase
            if (!self.searchText) {
                return reversedItems; // If no filter, return all items
            } else {
                // Filter the items based on the filter criteria
                return ko.utils.arrayFilter(reversedItems, function(item) {
                    return JSON.stringify(item).includes(self.searchText().toLowerCase()); // Case-insensitive match
                });
            }
        });

        // Function to reverse the observable array
        self.reverseArray = function() {
            self.items(self.items().slice().reverse());
        };

        self.filteredItemsLength = ko.computed(function() {
            return self.filteredItems().length;
        });
        self.fetchData();

        // Initialize validation
        self.errors = ko.validation.group(self);
    }
}

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

**[Explanation](https://answers.mindstick.com/blog/94/neural-network-simple-explanation-with-real-life-examples)**

`toggleLoader`**:** These methods handle the visibility of a loader animation, which is often used to indicate ongoing background processes like [AJAX requests](https://www.mindstick.com/forum/157895/what-is-the-difference-between-synchronous-and-asynchronous-ajax-requests-in-mvc).

`Observables`: Array to hold data fetched from the server. searchText, newTitle, newBody, userId, id: Observables to manage form inputs and [validation](https://www.mindstick.com/forum/2505/how-to-get-last-index-with-validation).\
`fetchData`: Makes an AJAX GET request to [fetch data](https://www.mindstick.com/forum/156768/how-to-fetch-data-using-union-method-in-linq) from the API. Toggles the loader before and after the request. Populates the items observable array with the fetched data.\
`addPost`: Validates form inputs. Determines if the action is to create a new post or update an [existing one](https://www.mindstick.com/interview/34109/how-do-you-generate-a-unique-file-name-to-avoid-overwriting-an-existing-one). Makes an AJAX POST/PUT request to add/update the post. Updates the items observable array with the new or updated post. Clears the form inputs and hides the modal on success.\
`deletePost`: Confirms with the user before deleting a post. Makes an AJAX DELETE request to delete the post. Removes the deleted post from the items observable array.\
`editPost`: Populates form inputs with the selected post's data. Opens the modal to allow editing.\
`filteredItems`: Computes the filtered items based on searchText. Reverses the items array to show the latest items first. Filters the items based on the search criteria.\
`reverseArray`: Reverses the order of the items observable array.\
`filteredItemsLength`: Computes the length of the filtered items.

## Read more

[**Conditional html heading tag based on variable in knockout.js**](https://www.mindstick.com/forum/160797/conditional-html-heading-tag-based-on-variable-in-knockout-js)

[**How knockout.js variables different from javascript regular variables**](https://www.mindstick.com/forum/160796/how-knockout-js-variables-different-from-javascript-regular-variables)

[**How to integrate Knockout.js with other JavaScript libraries or frameworks?**](https://www.mindstick.com/forum/160793/how-to-integrate-knockout-js-with-other-javascript-libraries-or-frameworks)

[**How would you handle data fetching and updating in a Knockout.js application?**](https://www.mindstick.com/articles/336251/how-would-you-handle-data-fetching-and-updating-in-a-knockout-js-application)

---

Original Source: https://www.mindstick.com/articles/336272/crud-operation-in-knockoutjs-using-ajax

Copyright © MindStick Software Pvt. Ltd. This Markdown version is provided for developers, AI systems, and offline reading.
