Welcome back! In the last lesson, you learned how to build basic endpoints to retrieve recipes from your database, including fetching a single recipe by its ID and listing recipes with pagination. These are the building blocks of any recipe API.
Now, let’s take things a step further. Imagine you open a cooking app and want to find recipes that use only the ingredients you have in your kitchen. Or maybe you want to see the steps of a recipe as a list so you can follow them one by one. These features make your app much more helpful and user-friendly.
In this lesson, you’ll learn how to:
- Search for recipes by a list of ingredients.
- Return recipe steps as an array, not just a long string.
These skills will help you build a smarter, more flexible recipe API.
Before we dive in, let’s quickly remind ourselves how our data is organized. This will help you understand how searching and filtering work.
- Recipe: Each recipe has an
id, aname, astepsfield (which is a string with all the steps), and a list ofingredients. - Ingredient: Each ingredient has an
idand aname. Ingredients are linked to recipes through a many-to-many relationship. - Review: Each review is linked to a recipe and has a
rating.
Here’s a simplified look at the models (using SQLAlchemy):
- The
recipe_ingredienttable connects recipes and ingredients. - Each recipe can have many ingredients, and each ingredient can be used in many recipes.
Many apps and websites want to show recipe steps one at a time, not as a single long string. To help with this, you can create an endpoint that returns the steps as a list.
First, get the recipe by its ID:
- If the recipe does not exist, a 404 error is raised.
Assume the steps are stored as a string, with each step on a new line. You can split this string into a list:
split("\n")breaks the string into lines.strip()removes any extra spaces.- The list comprehension skips any empty lines.
Return the steps as a structured response using a response model. First, define the response classes (Pydantic models) for the steps. For example::
Then, you can use these classes to build and return the steps:
Example output:
This structure allows clients (like web or mobile apps) to easily loop through the steps and present them to users in a clear, step-by-step format.
Let’s say you want to find all recipes that use a specific set of ingredients, like "tomato" and "basil". You’ll need an endpoint that takes a list of ingredient names and returns only the recipes that use all of them.
First, you need to accept a list of ingredient names from the user. You can do this by creating a POST endpoint that expects a JSON body.
- The request body is parsed and validated using a
Pydanticmodel. - All ingredient names are converted to lowercase for case-insensitive matching.
Example input:
Next, you need to find the IDs of the ingredients that match the names provided.
- This code looks up all ingredients in the database whose names match the ones in the list.
- If no ingredients are found, we return an empty list.
Now, you want to find recipes that use all the specified ingredients. This is done using a subquery:
- The subquery counts how many of the requested ingredients each recipe uses.
- Only recipes where the count matches the number of ingredients requested are selected. This ensures the recipe uses all the ingredients.
Finally, you return the matching recipes using a response model and a serialization helper.
For the response model, we will use an array of RecipeOut, created earlier in this course:
For serializing the recipes, we can implement a simple function that builds a RecipeOut given a Recipe:
Finally, we can implement the response for the function
Example output:
If no recipes match, you’ll get an empty list: [].
In this lesson, you learned how to:
- Return recipe steps as an array, making it easier for users to follow each step.
- Search for recipes by a list of ingredients, returning only those that use all the specified ingredients.
These features make your recipe API much more useful and user-friendly. Up next, you’ll get to practice building and testing these endpoints yourself. This hands-on practice will help you solidify your understanding and prepare you for even more advanced features. Good luck!
