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:

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.