Interacting with APIs

Now that we understand what an API endpoint is, let’s try interacting with the Cat API directly. Enter an endpoint path below (like /v1/images/search?limit=1) to see the API response.

Try these examples:

Note

The response will be shown in JSON format, which is a common data format used by APIs. JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate.

Code
viewof method = Inputs.select(["GET"], {
  label: "HTTP Method",
  attributes: {
    class: "form-select mb-3"
  }
})

viewof endpoint = Inputs.text({
  label: "Endpoint path", 
  placeholder: "/v1/images/search?limit=1",
  value: "/v1/images/search?limit=1",
  attributes: {
    class: "form-control mb-3"
  }
})

// Function to make the API request
async function fetchFromApi(method, path) {
  const baseUrl = "https://api.thecatapi.com";
  try {
    const response = await fetch(`${baseUrl}${path}`);
    const status = {
        code: response.status,
        ok: response.ok,
        text: response.statusText
    };
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return { data, status };
  } catch (error) {
    return {
        data: { "Message": `Error: ${error.message}` },
        status: {
            code: 400,
            ok: false,
            text: "Bad Request"
        }
    };
  }
}

response = {
  const result = await fetchFromApi(method, endpoint);
  return result;
}

viewof prettyResponse = {
    let content;

    if (response.data.Message) {
        content = html`<div class="alert alert-warning m-0">${response.data.Message}</div>`;
    } else {
        content = html`<pre class="card-body m-0" style="background-color: #f8f9fa; max-height: 400px; overflow-y: auto;">${JSON.stringify(response.data, null, 2)}</pre>`;
    }

    const badgeClass = response.status.ok ? "bg-success" : "bg-danger";

  const container = html`<div class="card">
    <div class="card-header d-flex justify-content-between align-items-center">
      <span>Response</span>
      <span class="badge ${badgeClass}">${response.status.code} ${response.status.text}</span>
    </div>
    ${content}
  </div>`;
  return container;
}

In fact, with the same structure, we can interact with multiple APIs. Let’s try interacting with The Metropolitan Museum of Art Collection API to get the a list of objects ids from the collection.

Code
viewof methodParts = Inputs.select(["GET"], {
    label: "HTTP Method",
    attributes: {
        class: "form-select mb-3"
    }
})

viewof domain = Inputs.text({
  label: "Domain",
  placeholder: "collectionapi.metmuseum.org",
  value: "collectionapi.metmuseum.org",
  attributes: {
    class: "form-control mb-3"
  }
})

viewof path = Inputs.text({
    label: "Path",
    placeholder: "/public/collection/v1/search",
    value: "/public/collection/v1/search",
    attributes: {
        class: "form-control mb-3"
    }
})

viewof query = Inputs.text({
    label: "Query parameters",
    placeholder: "?q=cat",
    value: "q=cat",
    attributes: {
        class: "form-control mb-3"
    }
})

async function fetchFromApiParts(method, domain, path, query) {
    try {
        const baseUrl = `https://${domain}`;
        const url = `${baseUrl}${path}?${query}`;
        const response = await fetch(url);
        const status = {
            code: response.status,
            ok: response.ok,
            text: response.statusText
        };
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        return { data, status };
    } catch (error) {
        return {
            data: { "Message": `Error: ${error.message}` },
            status: {
                code: 400,
                ok: false,
                text: "Bad Request"
            }
        };
    }
}

responseParts = {
    const result = await fetchFromApiParts(methodParts, domain, path, query);
    return result;
}

viewof prettyResponseParts = {
    let content;
    if (responseParts.data.Message) {
        content = html`<div class="alert alert-warning m-0">${responseParts.data.Message}</div>`;
    } else {
        content = html`<pre class="card-body m-0" style="background-color: #f8f9fa; max-height: 400px; overflow-y: auto;">${JSON.stringify(responseParts.data, null, 2)}</pre>`;
    }
    
    const badgeClass = responseParts.status.ok ? "bg-success" : "bg-danger";
    
    const container = html`<div class="card">
        <div class="card-header d-flex justify-content-between align-items-center">
            <span>Response</span>
            <span class="badge ${badgeClass}">${responseParts.status.code} ${responseParts.status.text}</span>
        </div>
        ${content}
    </div>`;
    return container;
}

Now, take any ID from the result and use it to get the object details from the API.

Code
viewof methodDetails = Inputs.select(["GET"], {
    label: "HTTP Method",
    attributes: {
        class: "form-select mb-3"
    }
})

viewof domainDetails = Inputs.text({
    label: "Domain",
    placeholder: "collectionapi.metmuseum.org",
    value: "collectionapi.metmuseum.org",
    attributes: {
        class: "form-control mb-3"
    }
})

viewof pathDetails = Inputs.text({
    label: "Path",
    placeholder: "/public/collection/v1/objects/",
    value: "/public/collection/v1/objects/",
    attributes: {
        class: "form-control mb-3"
    }
})

viewof parameterDetails = Inputs.text({
    label: "Parameter",
    placeholder: "Write the object id here",
    value: "570744",
    attributes: {
        class: "form-control mb-3"
    }
})

async function fetchFromApiDetails(method, domain, path, parameter) {
    try {
        const baseUrl = `https://${domain}`;
        const url = `${baseUrl}${path}${parameter}`;
        const response = await fetch(url);
        const status = {
            code: response.status,
            ok: response.ok,
            text: response.statusText
        };
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        return { data, status };
    } catch (error) {
        return { error: error.message };
    }
}

responseDetails = {
    const result = await fetchFromApiDetails(methodDetails, domainDetails, pathDetails, parameterDetails);
    return result;
}

viewof prettyResponseDetailsContainer = {
    let content;
    if (responseDetails.data.Message) {
        content = html`<div class="alert alert-warning m-0">${responseDetails.data.Message}</div>`;
    } else {
        content = html`<pre class="card-body m-0" style="background-color: #f8f9fa; max-height: 400px; overflow-y: auto;">${JSON.stringify(responseDetails.data, null, 2)}</pre>`;
    }

    const badgeClass = responseDetails.status.ok ? "bg-success" : "bg-danger";

    const container = html`<div class="card">
        <div class="card-header d-flex justify-content-between align-items-center">
            <span>Response</span>
            <span class="badge ${badgeClass}">${responseDetails.status.code} ${responseDetails.status.text}</span>
        </div>
        ${content}
    </div>`;
    return container;
}

And that allows us to retrieve, for instance, the image of the object, that is stored in the primaryImage field.

Code
viewof primaryImage = {
    const primaryImage = responseDetails.status.ok ? responseDetails.data.primaryImageSmall : "https://placehold.co/600x400";
    if (primaryImage) {
        const img = html`<img src="${primaryImage}" alt="Primary Image">`;
        return img;
    } else {
        return html`<img src="https://placehold.co/600x400" alt="Placeholder">`;
    }

}

Nice! Now, let’s do an exercise to practice what we have learned.