Skip to main content

Improvements to our first REST API

Using flask_smorest.abort instead of returning errors manually

At the moment in our API we're doing things like these in case of an error:

app.py
@app.get("/store/<string:name>")
def get_store(name):
try:
# Here you might also want to add the items in this store
# We'll do that later on in the course
return stores[store_id]
except KeyError:
return {"message": "Store not found"}, 404

A small improvement we can do on this is use the abort function from Flask-Smorest, which helps us write these messages and include a bit of extra information too.

Add this import at the top of app.py:

app.py
from flask_smorest import abort

And then let's change our error returns to use abort.

app.py
@app.get("/store/<string:store_id>")
def get_store(store_id):
try:
# Here you might also want to add the items in this store
# We'll do that later on in the course
return stores[store_id]
except KeyError:
abort(404, message="Store not found.")

And here:

app.py
@app.get("/item/<string:item_id>")
def get_item(item_id):
try:
return items[item_id]
except KeyError:
abort(404, message="Item not found.")

Adding error handling on creating items and stores

At the moment when we create items and stores, we expect there to be certain items in the JSON body of the request.

If those items are missing, the app will return an error 500, which means "Internal Server Error".

Instead of that, it's good practice to return an error 400 and a message telling the client what went wrong.

To do so, let's inspect the body of the request and see if it contains the data we need.

Let's change our create_item() function to this:

app.py
@app.post("/item")
def create_item():
item_data = request.get_json()
# Here not only we need to validate data exists,
# But also what type of data. Price should be a float,
# for example.
if (
"price" not in item_data
or "store_id" not in item_data
or "name" not in item_data
):
abort(
400,
message="Bad request. Ensure 'price', 'store_id', and 'name' are included in the JSON payload.",
)
for item in items.values():
if (
item_data["name"] == item["name"]
and item_data["store_id"] == item["store_id"]
):
abort(400, message=f"Item already exists.")

item_id = uuid.uuid4().hex
item = {**item_data, "id": item_id}
items[item_id] = item

return item

And our create_store() function to this:

app.py
@app.post("/store")
def create_store():
store_data = request.get_json()
if "name" not in store_data:
abort(
400,
message="Bad request. Ensure 'name' is included in the JSON payload.",
)
for store in stores.values():
if store_data["name"] == store["name"]:
abort(400, message=f"Store already exists.")

store_id = uuid.uuid4().hex
store = {**store_data, "id": store_id}
stores[store_id] = store

return store