Recipe app - Help with fetching data from API (Javascript)

Hi there

I’m trying to build a small, rather simple recipe app with Javascript, which would just allow the user to 1) see a display of random recipes on the landing page, 2) open a recipe card to display the info about the recipe, 3) search for a recipe.

For this I’m using the spoonacular API.

I got the first step done, and part of the second step. So I can show random recipes, and also render specific recipe data: ready in xx minutes, how many servings, the type of dish, and the cooking instructions. However, where I’m struggling is to fetch the ingredients needed for each recipe.

Unlike other properties of the recipe object that is returned for each call, there isn’t any property with, say, an array of all the ingredients for that recipe. Instead, each ingredient is separately and deeply nested within the analysed instructions of the recipe object. So, I wasn’t able to access the ingredients that way.

No matter, I thought, since the API has a few endpoints that could help with that! But all of the attempts I made to make use of one endpoint or the other got me nowhere.

The code can be found on my GitHub repo and the website is temporarily hosted here.
However, let me here below strip the code of all the (not necessarily useful) comments to focus on the core issue.

Heads-up: I’ve really tried to hide my API key in a separate module but I’m also running into errors to access it from the module. I know it’s not good practice, hope this is not too much of a big deal until I can find a fix for this.

First attempt: using the Parse Ingredients endpoint

async function getIngredients(recipe) {
  const res = await fetch(INGREDIENTS_URL, {
    method: 'POST',
    body: JSON.stringify(recipe),
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  const data = await res.json();

  console.log(data); // logs error 400 "We're so sorry, something went wrong. If this error persists, please contact us." if I pass in the recipe object, or 400 "The form parameter 'ingredientList' must not be null" if I pass in the recipe.title.

I’m not sure I understand how the endpoint works exactly, the documentation confused me. Given the second error message, I understand that the ingredientList parameter needs a value, so is the value the issue?

Second attempt: using the Get Recipe Ingredients by ID endpoint

async function getIngredients(id) {
const res = fetch('${id}/ingredientWidget.json');
const data = await res.json();
console.log(data); // logs Response {type: "cors", url: "…aa4f658af5c757103cc87a&${id}/ingredientWidget.json", redirected: false, status: 200, ok: true, …}

My money is actually on this endpoint and I’m not getting an error, but also not the data.
I believe this has to do with the id which or some reason is not rendered in the URL? But when I log the id in the function where I call getIngredients(id), it is logged correctly, so I’m confused.

Either way, my intention was to call getIngredients() within the showRecipes() function, passing either the recipe title (first attempt) or the recipe id (second attempt).

const main = document.getElementById('main');
const form = document.getElementById('form');
const search = document.getElementById('search');

const API_URL =

const SEARCH_API =

// Get initial recipes
async function getRecipes(url) {
  const res = await fetch(url);
  const data = await res.json();


function showRecipes(recipes) {
  main.innerHTML = '';

  recipes.forEach((recipe) => {
    const {
    } = recipe;

    // Get ingredients — the below is just for testing purposes so not yet rendered in the HTML

// First attempt    
    // getIngredients(recipe) logs error 400 "We're so sorry, something went wrong. If this error persists, please contact us."
 // getIngredients(recipe.title) logs error 400  "The form parameter 'ingredientList' must not be null"

// Second attempt
    // getIngredients(id); // logs Response {type: "cors", url: "…aa4f658af5c757103cc87a&${id}/ingredientWidget.json", redirected: false, status: 200, ok: true, …}

    if (recipe.image !== undefined) {
      const recipeEl = document.createElement('div');
      recipeEl.innerHTML = ` <img
      <h3 class="recipe-title">${title}</h3>
      <div class="recipe-text">
        <div class="recipe-info">
          <p><i class="far fa-clock"></i>${readyInMinutes} min</p>
          <p><i class="fas fa-utensils"></i>${servings} servings</p>
          <p><i class="fas fa-book-reader"></i>${formatDishTypes(
        <div class="ingredients">
          <p>Ingredients list</p>
        <div class="recipe-instructions">
          <h4>Cooking Steps</h4>


    const cards = document.querySelectorAll('.recipe-card');

    cards.forEach((card) => { 
      card.addEventListener('click', () => {
    function removeActiveClasses() {
      cards.forEach((card) => card.classList.remove('active'));

Thank you for any help!

1 Like

I eventually managed to hide my API key using modules, but also just noticed I can’t edit my post above.

Also I made some new progress with the code and some things have unlocked themselves since my first post - I’ll be updating the code on my Github for now but because it takes a bit of time to make it readable without all my tests/console.logs like in my previous post, I won’t update it here for now. Just so you know then, the above code isn’t the updated version!