Recipe Finder App with HTML, CSS & JavaScript

30 DAYS 30 PROJECT CHALLENGE

Day #27

Project Overview

The Recipe Finder App is a beginner-friendly project built with HTML, CSS, and JavaScript that allows users to search for recipes using a keyword. This project uses TheMealDB API to retrieves recipe information based on the search term entered by the user. Users can search for recipes such as pasta, pizza etc. and the application displays up to five matching recipes. Each recipe card includes an image, recipe name, category, area, and a View Recipe link. 

This project also displays a loading message while the API request runs and handles cases where no recipes are available. Therefore, this project gives beginners practical experience with fetch(), API responses, JSON data, template literals, dynamic HTML, and error handling.

Key Features

  • Recipe Search: Users can enter a recipe keyword and click the Search button to find matching recipes and then the application sends the entered keyword directly to TheMealDB API.
  • TheMealDB API Integration: This project uses TheMealDB API to retrieve recipe information dynamically. Therefore, the application can display real recipe data without storing the recipes directly inside the HTML.
  • Default Search: If the user leaves the search field empty, JavaScript automatically uses "pasta" as the search query. 
  • Recipe Images: Each recipe card displays an image using the strMealThumb value returned by the API. The CSS also keeps the image at a consistent height with object-fit: cover.
  • Recipe Name: The application displays the recipe name using the strMeal property from the API response. This gives each result a clear heading that users can identify quickly.
  • Recipe Category: Each card shows the category of the recipe, such as the value provided by strCategory. This gives users additional information about the type of dish.
  • View Recipe Link: Each recipe includes a View Recipe link that uses either strSource or strYoutube. The link opens in a new browser tab, allowing users to view the available recipe source separately.
  • Limited Results: The JavaScript uses slice(0, 5) to display only the first five recipes returned by the API. This keeps the result section manageable and prevents too many cards from appearing at once.
  • Loading Message: The app displays "Loading..." as soon as a search begins. This provides immediate feedback while JavaScript waits for the API response.
  • No Results Handling: When the API does not return any meals, the application displays "No recipes found". As a result, users receive a clear message instead of an empty result section.
  • Error Handling: The .catch() block handles failed API requests and displays "Error loading recipes". This gives users useful feedback if the request cannot complete successfully.
  • Dynamic Recipe Cards: JavaScript creates the recipe cards from the data returned by the API. Therefore, the same HTML structure can display different recipes based on each search.
  • Clean Card Layout: The CSS uses separate recipe cards with borders, rounded corners, images, content spacing, and links. This makes the returned recipes easy to scan and compare.

What You'll Learn

  • How to connect a frontend project with TheMealDB API.
  • How to use fetch() to request recipe data.
  • How to create an API URL dynamically with a search query.
  • How encodeURIComponent() prepares the search term for the API URL.
  • How to convert an API response into JSON with res.json().
  • How to check whether the API returned recipe data.
  • How to use slice() to limit displayed results.
  • How to use forEach() to process multiple recipes.
  • How to access properties from API objects such as strMeal and strCategory.
  • How to generate dynamic HTML using template literals.
  • How to display API images and links dynamically.
  • How to use fallback values with the || operator.

HTML Code

HTML creates the basic structure of the Recipe Finder App. The .recipe-container holds the complete application, while the <h2> displays the Recipe Finder heading. Inside the .search-box, the #searchInput field allows users to enter a recipe keyword, and the Search button calls getRecipes() when clicked.

Additionally, the #recipeList div starts empty and provides the area where JavaScript displays the recipe cards. Finally, the page loads script.js after the main HTML structure so the JavaScript can access the search field and recipe list.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Recipe Finder App</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>

  <div class="recipe-container">
    <h2>Recipe Finder</h2>

    <div class="search-box">
      <input
        type="text"
        id="searchInput"
        placeholder="Search recipe (e.g. pasta, chicken)"
      />
      <button onclick="getRecipes()">Search</button>
    </div>

    <div id="recipeList" class="recipe-list"></div>
  </div>

  <script src="script.js"></script>
</body>
</html>

CSS Code

CSS controls the layout and appearance of the Recipe Finder App. The universal selector sets box-sizing: border-box and applies the Segoe UI font, while the body uses Flexbox to position the application at the top center of the page with some padding.

The .recipe-container creates the main white card with a fixed width, padding, rounded corners, and a shadow. Next, .search-box uses Flexbox to place the input and button together, while the input and button styles control their spacing, borders, padding, and rounded edges.

This .recipe-list arranges the recipe cards vertically with consistent gaps. Finally, .recipe-card controls the card border and rounded corners, while .recipe-card img keeps recipe images at a fixed height and the .content, heading, paragraph, and link selectors add spacing and typography to the recipe information, while .error displays failed searches in red.

* {
  box-sizing: border-box;
  font-family: "Segoe UI", sans-serif;
}

body {
  min-height: 100vh;
  background: #042453;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 40px;
}

.recipe-container {
  background: #ffffff;
  width: 450px;
  padding: 25px;
  border-radius: 14px;
  box-shadow: 0 20px 40px rgba(0,0,0,0.25);
}

h2 {
  text-align: center;
  margin-bottom: 15px;
}

.search-box {
  display: flex;
  margin-bottom: 15px;
}

.search-box input {
  flex: 1;
  padding: 10px;
  border-radius: 6px 0 0 6px;
  border: 1px solid #ccc;
}

.search-box button {
  padding: 10px 16px;
  border: none;
  border-radius: 0 6px 6px 0;
  background: #4f46e5;
  color: white;
  cursor: pointer;
}

.recipe-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.recipe-card {
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  overflow: hidden;
}

.recipe-card img {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.recipe-card .content {
  padding: 12px;
}

.recipe-card h4 {
  margin-bottom: 6px;
}

.recipe-card p {
  font-size: 13px;
  color: #555;
}

.recipe-card a {
  display: inline-block;
  margin-top: 8px;
  font-size: 13px;
  color: #4f46e5;
  text-decoration: none;
}

.error {
  color: red;
  text-align: center;
}

Javascript Code

JavaScript handles the complete recipe search process. First, the code selects the #recipeList element so it can update the results. The getRecipes() function then reads and trims the value from #searchInput. If the field is empty, it uses "chicken" as the default query.

Next, the function displays "Loading..." and sends a request to TheMealDB API using fetch() and encodeURIComponent(query). After receiving the response, res.json() converts it into JavaScript data. The code checks data.meals and displays "No recipes found" when the API does not return any recipes.

When recipes are available, the script clears the loading message and uses data.meals.slice(0, 5).forEach() to process the first five meals. For each meal, it creates a recipe card containing the thumbnail, meal name, category, area, and a View Recipe link. The link uses meal.strSource || meal.strYoutube, so the source URL appears when available and the YouTube URL acts as a fallback. Finally, the .catch() block displays "Error loading recipes" if the API request fails.

const recipeList = document.getElementById("recipeList");

function getRecipes() {
  const query =
    document.getElementById("searchInput").value.trim() || "chicken";

  recipeList.innerHTML = "Loading...";

  fetch(`https://www.themealdb.com/api/json/v1/1/search.php?s=${encodeURIComponent(query)}`)
    .then(res => res.json())
    .then(data => {
      if (!data.meals) {
        recipeList.innerHTML = `<p class="error">No recipes found</p>`;
        return;
      }

      recipeList.innerHTML = "";

      data.meals.slice(0, 5).forEach(meal => {
        recipeList.innerHTML += `
          <div class="recipe-card">
            <img src="${meal.strMealThumb}" alt="${meal.strMeal}">
            <div class="content">
              <h4>${meal.strMeal}</h4>
              <p><strong>Category:</strong> ${meal.strCategory}</p>
              <p><strong>Area:</strong> ${meal.strArea}</p>
              <a href="${meal.strSource || meal.strYoutube}" target="_blank">
                View Recipe
              </a>
            </div>
          </div>
        `;
      });
    })
    .catch(() => {
      recipeList.innerHTML = `<p class="error">Error loading recipes</p>`;
    });
}
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Related Projects

Day 25 : Stock Price UI (Mock / API)

Displays stock prices and daily changes using mock data with API-ready structure.

Concepts: UI state management, dynamic rendering, data simulation.

Day 29 : IP Address Tracker

Tracks and displays location details of an IP address using a public API.

Concepts: API integration, async data fetching, dynamic UI rendering.

Day 30 : Public Holiday Finder

Finds and displays public holidays by country and year using a public API.

Concepts: API integration, async data fetching, dynamic UI rendering.