Introduction: Enhancing Our Cooking API

Welcome back! In the last lesson, you learned how to make your cooking assistant more accessible by generating audio instructions using the gTTS library. Now, we are going to make your API even more useful and interactive by adding two new features: the ability to list all available ingredients and to let users submit reviews for recipes.

These features are important for any smart cooking app. Listing ingredients helps users see what’s available or filter recipes, while reviews allow users to share feedback and help others find the best recipes. By the end of this lesson, you will know how to build and test these endpoints in Flask.

Quick Recall: Flask Endpoints Refresher

Before we dive in, let’s quickly remind ourselves how Flask endpoints work. In Flask, an endpoint is a function that runs when a user visits a certain URL. You define endpoints using the @app.route decorator, and you can specify which HTTP methods (like GET or POST) the endpoint should accept.

For example, here’s a simple GET endpoint:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/hello', methods=['GET'])
def say_hello():
    return jsonify({'message': 'Hello, world!'})
  • The @app.route('/hello', methods=['GET']) line tells Flask to run the say_hello function when someone visits /hello using the GET method.
  • The function returns a JSON response.

In this lesson, you’ll use similar patterns to build your new endpoints.

Building the Ingredients Endpoint

Let’s start by creating an endpoint that lists all unique ingredients in your database. This will be a GET endpoint, which means users will request data from it.

Step 1: Define the Route

First, you need to define a new route for your endpoint. In your Flask app, you can use a blueprint or the main app object. Here’s how you define the route using a blueprint:

@routes.route('/api/ingredients', methods=['GET'])
def get_all_ingredients():
    # Implementation will go here
  • @routes.route('/api/ingredients', methods=['GET']) sets up the endpoint at /api/ingredients and only allows GET requests.
Step 2: Query the Database

Next, you want to get all unique ingredient names from the database. You can use SQLAlchemy to do this:

ingredient_names = db.session.query(Ingredient.name).distinct().all()
  • db.session.query(Ingredient.name) asks the database for all ingredient names.
  • .distinct() ensures you only get each name once, even if it appears in multiple recipes.
  • .all() returns all results as a list of tuples.
Step 3: Format the Results

The result from the database will look like this: [('egg',), ('flour',), ('milk',)]. You want to return a simple list of strings. You can do this with a list comprehension:

return jsonify([name[0] for name in ingredient_names]), 200
  • [name[0] for name in ingredient_names] extracts the string from each tuple.
  • jsonify(...) turns the list into a JSON response.
  • , 200 sets the HTTP status code to 200 (OK).
Full Example

Here’s the complete endpoint:

@routes.route('/api/ingredients', methods=['GET'])
def get_all_ingredients():
    ingredient_names = db.session.query(Ingredient.name).distinct().all()
    return jsonify([name[0] for name in ingredient_names]), 200

Example Output:

If your database has eggs, flour, and milk, a GET request to /api/ingredients will return:

["egg", "flour", "milk"]

This endpoint is now ready to help users see all available ingredients.

Building the Reviews Endpoint

Now, let’s add a way for users to submit reviews for recipes. This will be a POST endpoint, which means users will send data to it.

Step 1: Define the Route

Set up the route for submitting reviews:

@routes.route('/api/reviews', methods=['POST'])
def submit_review():
    # Implementation will go here
  • This endpoint will accept POST requests at /api/reviews.
Step 2: Get and Validate the Data

You need to get the data the user sends and make sure it’s valid. The user should send a recipe_id and a rating (between 1 and 5).

data = request.get_json()

recipe_id = data.get('recipe_id')
rating = data.get('rating')

if not recipe_id or not rating or not (1 <= rating <= 5):
    return jsonify({'error': 'Invalid recipe_id or rating'}), 400

# Check if the recipe exists
recipe = Recipe.query.get(recipe_id)
if not recipe:
    return jsonify({'error': 'Recipe not found'}), 404
  • request.get_json() gets the data sent in the request.
  • data.get('recipe_id') and data.get('rating') extract the values.
  • The if statement checks that both values are present and that the rating is between 1 and 5.
  • If the data is invalid, the function returns an error message and a 400 (Bad Request) status.
  • Recipe.query.get(recipe_id) queries the recipe. If we try to submit a review for a non-existant recipe (the recipe variable is None), we will return 404 status.
Step 3: Save the Review

If the data is valid, you can create a new review and save it to the database:

review = Review(recipe_id=recipe_id, rating=rating)
db.session.add(review)
db.session.commit()
  • Review(recipe_id=recipe_id, rating=rating) creates a new review object.
  • db.session.add(review) adds it to the database session.
  • db.session.commit() saves it to the database.
Step 4: Return a Confirmation

Finally, return a message to let the user know the review was saved:

return jsonify({'message': 'Review submitted successfully'}), 201
  • The message confirms the review was saved.
  • The status code 201 means “Created.”
Full Example

Here’s the complete endpoint:

@routes.route('/api/reviews', methods=['POST'])
def submit_review():
    data = request.get_json()

    recipe_id = data.get('recipe_id')
    rating = data.get('rating')

    if not recipe_id or not rating or not (1 <= rating <= 5):
        return jsonify({'error': 'Invalid recipe_id or rating'}), 400

    review = Review(recipe_id=recipe_id, rating=rating)
    db.session.add(review)
    db.session.commit()

    return jsonify({'message': 'Review submitted successfully'}), 201

Example Input:

A user might send this JSON to /api/reviews:

{
  "recipe_id": 1,
  "rating": 5
}

Example Output:

{
  "message": "Review submitted successfully"
}

If the input is invalid, for example, if the rating is 6:

{
  "recipe_id": 1,
  "rating": 6
}

The output will be:

{
  "error": "Invalid recipe_id or rating"
}
Summary and Practice Preview

In this lesson, you learned how to add two important features to your smart cooking API: listing all unique ingredients and allowing users to submit reviews for recipes. You saw how to define new endpoints, query the database, validate user input, and return clear JSON responses.

Next, you’ll get a chance to practice what you’ve learned. The upcoming exercises will help you reinforce these skills by guiding you through building and testing similar endpoints on your own. This hands-on practice will ensure you’re comfortable working with Flask routes, database queries, and handling user data. Good luck!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal