This small tutorial was built on top of my previous code sample that configures a simple Flask app with testing:

How to add basic unit test to a Python Flask app using Pytest
Carlos Villavicencio γ» May 18 '20 γ» 2 min read
There are many ways to build REST APIs, the most common is to have a Django app with DRF. Other people are trying FastAPI (I need to take a closer look at it, maybe in a future post).
But if you are using a Flask based app, I recently tried Flask-RESTX library which includes some great features:
- Swagger documentation (Heck yes!)
- Response marshalling
- Request parsing
- Error handling, logging and blueprint support. Neat Flask integration.
In this demo, I'll show you how to set up a quick REST API, with Swagger documentation, request parsing and simple response formatting.
Let's start by initializing the blueprint and defining the api object in a new module. I named this one as api.py
.
blueprint = Blueprint("api", __name__, url_prefix="/api/v1")
api = Api(
blueprint,
version="1.0",
title="Mini REST API",
description="A mini REST API",
)
ns = api.namespace("items", description="Item operations")
api.add_namespace(ns)
Flask-RESTX support Flask Blueprint and they are really simple to implement.
My application is served at http://localhost:5000
but my API base URL will be http://localhost:5000/api/v1
. This is also the page where you can find the Swagger docs.
Next, let's write the base models. My sample API will manage Items and Details objects, so I need to write the models that will be in charge of presenting them in the API standard response.
detail_model = api.model("Detail", {"id": fields.Integer, "name": fields.String})
item_model = api.model(
"Item",
{
"id": fields.Integer,
"name": fields.String,
"details": fields.List(fields.Nested(detail_model)),
},
)
The idea of writing models is to use Flask-RESTX response marshalling, so no matter if our objects scale, the response will always be as we document it on our models. Flask-RESTX includes a lot of tools for this such as renaming attributes, complex, custom, and nested fields, and more.
The final set up step is to write the request parser.
item_parser = api.parser()
item_parser.add_argument("id", type=int, location="form")
item_parser.add_argument("name", type=str, location="form")
detail_parser = api.parser()
detail_parser.add_argument("id", type=int, location="form")
detail_parser.add_argument("name", type=str, location="form")
In a similar way as before, we make use of Flask-RESTX request parser to read and validate values that we expect to receive in our endpoints. In this case I plan to implement two object APIs that will append elements to our database objects. (Our database is a simple in-memory object π
)
memory_object = [
{
"id": 1,
"name": "Item 1",
"details": [
{"id": 1, "name": "Detail 1"},
{"id": 2, "name": "Detail 2"},
],
}
]
Now it's time to implement our APIs. The first API I want to build is the one that manages the items. I will call this ItemApi
and the route will be /
which means the root of the namespace items
.
@ns.route("/")
class ItemsApi(Resource):
"""
API for handling the Item list resource
"""
@api.response(HTTPStatus.OK.value, "Get the item list")
@api.marshal_list_with(item_model)
def get(self) -> list[Item]:
"""
Returns the memory object
"""
return memory_object
@api.response(HTTPStatus.OK.value, "Object added")
@api.expect(item_parser)
def post(self) -> None:
"""
Simple append something to the memory object
"""
args = item_parser.parse_args()
memory_object.append(args)
This will enable two endpoints:
Endpoint | Method | Parameters | Returns |
---|---|---|---|
/api/v1/items/ |
GET | None | list of item_model
|
/api/v1/items/ |
POST | As defined on item_parser
|
None |
All decorators are provided by Flask-RESTX. HTTPStatus
class is provided by the http
module. Pretty simple huh?, let's build the second one.
This one will manage a single item resource. So, to get its data and add details we need the following implementation:
@ns.route("/<int:item_id>")
class ItemApi(Resource):
"""
API for handling the single Item resource
"""
@api.response(HTTPStatus.OK.value, "Get the item list")
@api.response(HTTPStatus.BAD_REQUEST.value, "Item not found")
@api.marshal_with(item_model)
def get(self, item_id: int) -> Item:
"""
Returns the memory object
"""
try:
return self._lookup(item_id)
except StopIteration:
return api.abort(HTTPStatus.BAD_REQUEST.value, "Item not found")
def _lookup(self, item_id):
return next(
(item for item in memory_object if item["id"] == item_id),
)
@api.response(HTTPStatus.NO_CONTENT.value, "Object added")
@api.response(HTTPStatus.BAD_REQUEST.value, "Item not found")
@api.expect(detail_parser)
def post(self, item_id: int) -> None:
"""
Simple append details to the memory object
"""
args = item_parser.parse_args()
try:
if item := self._lookup(item_id):
item["details"].append(args)
return None
except StopIteration:
return api.abort(HTTPStatus.BAD_REQUEST.value, "Item not found")
This will enable two more endpoints:
Endpoint | Method | Parameters | Returns |
---|---|---|---|
/api/v1/items/<item_id> |
GET | None | a single item_model resource. |
/api/v1/items/<item_id> |
POST | As defined on detail_parser
|
None |
To wrap up our application, you only need to import the module at app.py
and register the Blueprint.
from api import blueprint
app = Flask(__name__) # This line already exists
app.register_blueprint(blueprint)
You can fork and play with this example using this repo:
Mini example of Flask and Flask-RESTX
This is a examle repository for my article.
Setup
Create and activate the virtual environment
virtualenv venv
source venv/bin/activate
Run the server
FLASK_ENV=development flask run
Check out the Swagger documentation and playground at
http://localhost:5000/api/v1/
Run the tests
python -m pytest
The server will be up on http://localhost:5000 and the API landing page will be available on http://127.0.0.1:5000/api/v1/.
Requirements
Python >= 3.9
License
I also added some unit tests and type annotations for your delight π.
Any feedback or suggestions are welcome and I'll be happy to answer any question you may have.
Discussion (0)