How to Create a Random Meal Generator Webapp: A Comprehensive Guide for Full-Stack Developers
Are you stuck in a cooking rut, making the same meals on repeat? You‘re not alone. Studies show that on average, home cooks cycle through the same 9 recipes again and again.[^1] No wonder so many of us experience "cooking fatigue"!
One solution? Trying new recipes. Variety is the spice of life after all! But finding recipe inspiration can be a challenge in itself. That‘s where a Random Meal Generator comes in – an app that suggests recipe ideas for you at the click of a button.
By the end of this in-depth tutorial, you‘ll have built a fully-functional Random Meal Generator web app from scratch using HTML, CSS, JavaScript and a 3rd-party API. More importantly, you‘ll gain practical experience with key full-stack development concepts like:
- Separating concerns between structure (HTML), presentation (CSS) and behavior (JS)
- Integrating with REST APIs
- Dynamically rendering data in the UI
- Error handling and data validation
- Web accessibility and responsive design
- Performance optimization
- Deployment to the web
While we‘ll be focusing on front-end code, the skills you learn will set you up to build full-stack applications that include back-end APIs, databases, user authentication, and more.
So let‘s get started!
Planning the App Structure
Before diving into code, let‘s plan out the high-level structure of our app. It will consist of 3 main parts:
- The user interface (HTML/CSS) with a button to generate a random meal and a container to display the meal data
- A JavaScript function to fetch a random meal from the API
- A separate function to extract the relevant data from the API response and render it in the UI
We can sketch out some pseudo-code to represent this flow:
When user clicks "Generate" button
Make GET request to API /random endpoint
If request succeeds
Extract meal data from response
Generate HTML markup for meal
Insert HTML into meal container
If request fails
Display error message to user
Separating concerns like this makes our code more modular, reusable and maintainable – key principles in software development.[^2]
Scaffolding the UI
Let‘s start by creating the basic structure of our app in HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Random Meal Generator</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<p>Click below to generate a random meal idea!</p>
<button id="btn-generate">Get Random Meal</button>
<div id="meal-container" aria-live="polite"></div>
</main>
<script src="script.js"></script>
</body>
</html>
Some best practices to note:
- We set the
lang
attribute on the<html>
tag to declare the language of the page (English). This is important for accessibility.[^3] - We include a
<meta charset>
tag to specify the character encoding (UTF-8) and a<meta viewport>
tag to ensure proper rendering on mobile devices. - In the
<body>
, we use semantic HTML5 tags like<main>
to give meaning and structure to our content. - We link to external CSS and JavaScript files to keep our code separated and organized.
- The meal container
<div>
has anaria-live
attribute to notify screen readers when the content is updated dynamically.
Fetching Data from the API
With our UI scaffolded, let‘s write the JavaScript to retrieve a random meal from the API.
We‘ll be using TheMealDB API, a free and open-source database of recipes from around the world. It provides a /random
endpoint that returns a single random meal in JSON format.
Here‘s the code to make a request to this endpoint and handle the response:
const API_URL = ‘https://www.themealdb.com/api/json/v1/1/random.php‘;
const getRandomMeal = async () => {
try {
const response = await fetch(API_URL);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.meals[0];
}
catch(error) {
console.error(`Fetch problem: ${error.message}`);
}
};
Let‘s break this down:
- We define a constant for the API URL to make it easy to change if needed.
- We create an async function
getRandomMeal()
to fetch data from the API. - Inside the function, we use the Fetch API to make a GET request to the
/random
endpoint. - We check if the response is "ok" (status in the 200-299 range). If not, we throw an error.
- If the response is ok, we parse the JSON data using
response.json()
. - The API returns an object with a
meals
property containing an array with one meal object. We return this meal object. - If anything goes wrong with the request/response, we catch the error and log it to the console.
Using async/await
syntax makes our asynchronous code read more like synchronous code, improving readability. We also make sure to handle any errors that may occur.[^4]
Rendering the Meal Data
Now that we can get a random meal from the API, we need to display its data in the UI. This involves two steps:
- Extracting the relevant data properties from the meal object
- Generating HTML markup with the extracted data
Let‘s create a renderMeal()
function to handle this:
const renderMeal = (meal) => {
const ingredients = [];
// Extract up to 20 ingredients & measures from meal object
for (let i = 1; i <= 20; i++) {
const ingredient = meal[`strIngredient${i}`];
const measure = meal[`strMeasure${i}`];
if (ingredient) {
ingredients.push(`<li>${measure} ${ingredient}</li>`);
}
}
const html = `
<article class="meal">
<h2>${meal.strMeal}</h2>
<img src="${meal.strMealThumb}" alt="${meal.strMeal}">
<ul>
${ingredients.join(‘‘)}
</ul>
<p>${meal.strInstructions}</p>
</article>
`;
document.getElementById(‘meal-container‘).innerHTML = html;
};
Let‘s break this down:
- We create an empty array to hold the ingredients for the meal.
- We loop through the meal object properties to find up to 20 ingredients and their corresponding measures. The API provides these as properties named
strIngredient1
,strMeasure1
,strIngredient2
,strMeasure2
, etc. - If an ingredient exists (is not an empty string or null), we create an
<li>
element for it and push it into the ingredients array. - After collecting the ingredients, we construct the HTML markup for the meal using template literals. This includes the title, image, ingredients list, and instructions. Importantly, we set the
alt
attribute on the image for accessibility. - Finally, we set the
innerHTML
of themeal-container
div to the generated HTML.
This approach offers more control over the rendering process compared to using a templating engine or framework. However, setting innerHTML
directly can pose security risks if the inserted content includes user input. Always sanitize data before rendering it![^5]
Putting it All Together
With our API function and render function defined, we can now connect them to the "Generate" button:
document.getElementById(‘btn-generate‘).addEventListener(‘click‘, async () => {
try {
const meal = await getRandomMeal();
renderMeal(meal);
}
catch(error) {
console.error(error);
alert(‘Failed to generate meal. Please try again!‘);
}
});
When the button is clicked, we call getRandomMeal()
to fetch a random meal from the API. If successful, we pass the returned meal object to renderMeal()
to display it. If an error occurs, we log it and display a user-friendly error message.
Styling the App
To polish off our app, let‘s add some CSS:
/* Global Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
color: #333;
}
main {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
font-size: 2em;
margin-bottom: 20px;
}
p {
margin-bottom: 20px;
}
button {
background-color: #007BFF;
color: #FFF;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #0056B3;
}
/* Meal Styles */
.meal {
margin-top: 40px;
}
.meal h2 {
font-size: 1.5em;
margin-bottom: 10px;
}
.meal img {
width: 100%;
max-width: 400px;
height: auto;
margin-bottom: 20px;
}
.meal ul {
margin-left: 20px;
margin-bottom: 20px;
}
.meal li {
margin-bottom: 10px;
}
.meal p {
white-space: pre-line;
}
/* Media Queries */
@media screen and (max-width: 600px) {
h1 {
font-size: 1.5em;
}
.meal h2 {
font-size: 1.2em;
}
}
Some key things to note:
- We use the
box-sizing: border-box
property to include padding and borders in an element‘s total width and height. This makes sizing more intuitive. - We set a
max-width
on the<main>
element to limit the content width on larger screens, improving readability. - We style the "Generate" button with a blue background, white text, and rounded corners. We also add a hover effect to provide visual feedback.
- For the meal image, we set a
max-width
in pixels and awidth
in percentage. This makes the image responsive while constraining its maximum size. - We use the
white-space: pre-line
property on the instructions paragraph to preserve line breaks from the API data. - Finally, we include a media query to adjust the font sizes on smaller screens, ensuring the content remains readable.
Remember, CSS is highly customizable. Feel free to experiment with colors, typography, layout and more to make the app your own!
Making it Your Own
Now that you have a working Random Meal Generator, it‘s time to customize and extend it. Here are a few ideas to get you started:
- Add search functionality to find meals by name or ingredient
- Filter meals by category, area, or dietary requirements (e.g. vegetarian, gluten-free)
- Allow users to save favorite meals to local storage[^6]
- Implement a rating system for meals
- Generate a printable shopping list for the meal ingredients
- Create a weekly meal planner that generates a unique meal for each day
The possibilities are endless! Use this project as a starting point to explore new web development techniques and APIs.
For example, you could leverage the browser‘s geolocation API to detect the user‘s location and suggest meals popular in their area. Or integrate with a grocery delivery API to enable users to order ingredients directly from the app.
As a full-stack developer, you could take this project even further by building your own back-end API to store user-generated recipes, images, and ratings. You could implement user authentication and authorization to create a personalized experience.
The key is to start small, break down the problem into manageable pieces, and iterate. Don‘t be afraid to experiment, make mistakes, and learn as you go. That‘s the beauty of web development!
Deploying Your App
Once you‘re happy with your app, you‘ll probably want to share it with the world. To do this, you need to deploy it to a web server.
There are many options for deploying static websites (HTML, CSS and client-side JS) for free. Some popular choices include:
These services allow you to connect your GitHub repository and automatically deploy your app whenever you push changes. They also provide custom domain support, SSL certificates, and other features to make your app more professional.
If your app includes a back-end API or database, you‘ll need a more robust deployment solution like:
These platforms support a wide range of server-side technologies and provide tools for managing your app‘s infrastructure and scaling.
No matter which deployment route you choose, remember to optimize your app‘s performance and security before releasing it into the wild. This includes minifying your code, compressing images, and using HTTPS to encrypt data in transit.
Wrap-up
Congratulations! You‘ve not only built a functional Random Meal Generator but also gained valuable web development skills in the process.
Let‘s recap what we‘ve covered:
- Planning and structuring a web app
- Using semantic HTML to markup content
- Styling a page with CSS
- Integrating with a 3rd-party API using JavaScript
- Parsing JSON data and dynamically updating the DOM
- Handling asynchronous operations with Promises and async/await
- Debugging and error handling
- Deploying a static web app
These concepts form the foundation of modern full-stack web development. With these skills under your belt, you‘re well-equipped to tackle more complex projects and continue growing as a developer.
Remember, learning to code is a journey, not a destination. Keep practicing, exploring new technologies, and building projects you‘re passionate about. The more you code, the more you‘ll learn and the better you‘ll become.
So what are you waiting for? Go out there and build something amazing!