Javascript API Calls

Prerequisites

  • Complete Tutorial: Create a web API
  • Familiarity with CSS, HTML, and JavaScript

Calling the web API with JavaScript

In this section, you’ll create an HTML page that includes forms for creating and managing to-do items. You’ll attach event handlers to elements on the page, which will result in HTTP requests to the web API’s action methods. To initiate each HTTP request, you’ll use the Fetch API’s fetch function.

The fetch function returns a Promise object that contains an HTTP response, represented as a Response object. A common pattern is to extract the JSON response body by using the json function on the Response object. JavaScript will then update the page with the details from the web API’s response.

The simplest fetch call accepts a single parameter representing the route. There’s an optional second parameter called the init object, which is used to configure the HTTP request.

Configure the app and serve static files

To begin, you need to configure the app to serve static files and enable default file mapping. Add the following highlighted code in Program.cs:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));

var app = builder.Build();

if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseAuthorization();

app.MapControllers();
app.Run();

Follow these steps to continue:

  1. Create a wwwroot folder in the project root.
  2. Create a css folder inside the wwwroot folder.
  3. Create a js folder inside the wwwroot folder.
  4. Add an HTML file named index.html to the wwwroot folder. Replace the contents of index.html with the following markup:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<link rel="stylesheet" href="css/site.css" />
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="editForm">
<h3>Edit</h3>
<form action="javascript:void(0);" onsubmit="updateItem()">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete?</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="js/site.js" asp-append-version="true"></script>
<script type="text/javascript">getItems();</script>
</body>
</html>
  1. Add a CSS file named site.css to the wwwroot/css folder. Replace the contents of site.css with the following styles:
input[type='submit'], button, [aria-label] {
    cursor: pointer;
}

#editForm {
    display: none;
}

table {
    font-family: Arial, sans-serif;
    border: 1px solid;
    border-collapse: collapse;
}

th {
    background-color: #f8f8f8;
    padding: 5px;
}

td {
    border: 1px solid;
    padding: 5px;
}
  1. Add a JavaScript file named site.js to the wwwroot/js folder. Replace the contents of site.js with the following code:
const uri = 'api/todoitems';
let todos = [];

function getItems() {
    fetch(uri)
        .then(response => response.json())
        .then(data => _displayItems(data))
        .catch(error => console.error('Unable to get items.', error));
}

function addItem() {
    const addNameTextbox = document.getElementById('add-name');
    const item = {
        isComplete: false,
        name: addNameTextbox.value.trim()
    };

    fetch(uri, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(item)
    })
        .then(response => response.json())
        .then(() => {
            getItems();
            addNameTextbox.value = '';
        })
        .catch(error => console.error('Unable to add item.', error));
}

function deleteItem(id) {
    fetch(`${uri}/${id}`, {
        method: 'DELETE'
    })
        .then(() => getItems())
        .catch(error => console.error('Unable to delete item.', error));
}

function displayEditForm(id) {
    const item = todos.find(item => item.id === id);
    document.getElementById('edit-name').value = item.name;
    document.getElementById('edit-id').value = item.id;
    document.getElementById('edit-isComplete').checked = item.isComplete;
    document.getElementById('editForm').style.display = 'block';
}

function updateItem() {
    const itemId = document.getElementById('edit-id').value;
    const item = {
        id: parseInt(itemId, 10),
        isComplete: document.getElementById('edit-isComplete').checked,
        name: document.getElementById('edit-name').value.trim()
    };

    fetch(`${uri}/${itemId}`, {
        method: 'PUT',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(item)
    })
        .then(() => getItems())
        .catch(error => console.error('Unable to update item.', error));

    closeInput();
    return false;
}

function closeInput() {
    document.getElementById('editForm').style.display = 'none';
}

function _displayCount(itemCount) {
    const name = (itemCount === 1) ? 'to-do' : 'to-dos';
    document.getElementById('counter').innerText = `${itemCount} ${name}`;
}

function _displayItems(data) {
    const tBody = document.getElementById('todos');
    tBody.innerHTML = '';
    _displayCount(data.length);

    const button = document.createElement('button');

    data.forEach(item => {
        let isCompleteCheckbox = document.createElement('input');
        isCompleteCheckbox.type = 'checkbox';
        isCompleteCheckbox.disabled = true;
        isCompleteCheckbox.checked = item.isComplete;

        let editButton = button.cloneNode(false);
        editButton.innerText = 'Edit';
        editButton.setAttribute('onclick', `displayEditForm(${item.id})`);

        let deleteButton = button.cloneNode(false);
        deleteButton.innerText = 'Delete';
        deleteButton.setAttribute('onclick', `deleteItem(${item.id})`);

        let tr = tBody.insertRow();
        let td1 = tr.insertCell(0);
        td1.appendChild(isCompleteCheckbox);

        let td2 = tr.insertCell(1);
        let textNode = document.createTextNode(item.name);
        td2.appendChild(textNode);

        let td3 = tr.insertCell(2);
        td3.appendChild(editButton);

        let td4 = tr.insertCell(3);
        td4.appendChild(deleteButton);
    });

    todos = data;
}

Lastly, to test the HTML page locally, you may need to make a change to the ASP.NET Core project’s launch settings:

  1. Open Properties/launchSettings.json.
  2. Remove the launchUrl property to force the app to open index.html – the project’s default file.
See also  A More Intelligent Approach to Mastering Javascript

This sample covers all the CRUD methods of the web API. Below are explanations of the different web API requests.

Get a list of to-do items

In the following code, an HTTP GET request is sent to the api/todoitems route:

fetch(uri)
    .then(response => response.json())
    .then(data => _displayItems(data))
    .catch(error => console.error('Unable to get items.', error));

When the web API returns a successful status code, the _displayItems function is invoked. Each to-do item in the array parameter accepted by _displayItems is added to a table with “Edit” and “Delete” buttons. If the web API request fails, an error is logged to the browser’s console.

Add a to-do item

In the following code:

function addItem() {
    const addNameTextbox = document.getElementById('add-name');
    const item = {
        isComplete: false,
        name: addNameTextbox.value.trim()
    };

    fetch(uri, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(item)
    })
        .then(response => response.json())
        .then(() => {
            getItems();
            addNameTextbox.value = '';
        })
        .catch(error => console.error('Unable to add item.', error));
}

When the web API returns a successful status code, the getItems function is invoked to update the HTML table. If the web API request fails, an error is logged to the browser’s console.

Update a to-do item

Updating a to-do item is similar to adding one, with two significant differences:

  • The route is suffixed with the unique identifier of the item to update (e.g., api/todoitems/1).
  • The HTTP action verb is PUT, as indicated by the method option.
fetch(`${uri}/${itemId}`, {
    method: 'PUT',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(item)
})
    .then(() => getItems())
    .catch(error => console.error('Unable to update item.', error));

Delete a to-do item

To delete a to-do item, set the request’s method option to DELETE and specify the item’s unique identifier in the URL.

fetch(`${uri}/${id}`, {
    method: 'DELETE'
})
    .then(() => getItems())
    .catch(error => console.error('Unable to delete item.', error));

Now you’re ready to move on to the next tutorial to learn how to generate web API help pages.

See also  Building a Real-Time Chat Room with Javascript