How to set up a REST API in Flask in 5 steps
Tags: python, rest, api, flask
Published: 2021-11-05 22:07:25

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

{% link https://dev.to/po5i/how-to-add-basic-unit-test-to-a-python-flask-app-using-pytest-1m7a %}

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](https://flask-restx.readthedocs.io/en/latest/) 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`.

```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](https://flask-restx.readthedocs.io/en/latest/scaling.html#use-with-blueprints).

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.

![Swagger landing page sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c65hlxxmj6d8uya5y6gm.png) 

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.

```py
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](https://flask-restx.readthedocs.io/en/latest/marshalling.html), 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.

```py
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](https://flask-restx.readthedocs.io/en/latest/parsing.html) 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 😅)

```py
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`.


```py
@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:

```py
@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.

```py
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:

{% github po5i/flask-mini-rest %}

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.