Skip to content

Basic Usage

This chapter will cover the primary usage of APIFlask.

Prerequisites

  • Python 3.7+
  • Flask 1.1+

You also need to know the basic of Flask. Here are some useful free resources to learn Flask:

Installation

$ pip3 install apiflask
> pip install apiflask

Python dependency management tools

The command above use pip to install APIFlask, you can also use other dependencies management tools such as Poetry, Pipenv, PDM, etc.

Create an app instance with APIFlask class

Similar to what you did to create a Flask app instance, you will need to import APIFlask class from apiflask package, then create the app instance from the APIFlask class:

from apiflask import APIFlask

app = APIFlask(__name__)


@app.get('/')
def index():
    return {'message': 'hello'}

The default title and version of the API will be APIFlask and 0.1.0; you can pass the title and the version arguments to change these settings:

app = APIFlask(__name__, title='Wonderful API', version='1.0')

To run this application, you can save it as app.py, then run the flask run command:

$ flask run
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

If your script's name isn't app.py, you will need to declare which application should be started before execute flask run. See the note below for more details.

Assign the specific application to run with FLASK_APP

In default, Flask will look for an application instance called app or application or application factory function called create_app or make_app in module/package called app or wsgi. That's why I recommend naming the file as app.py. If you use a different name, then you need to tell Flask the application module path via the environment variable FLASK_APP. For example, if your application instance stored in a file called hello.py, then you will need to set FLASK_APP to the module name hello:

$ export FLASK_APP=hello
> set FLASK_APP=hello
> $env:FLASK_APP="hello"

Similarly, If your application instance or application factory function stored in mypkg/__init__.py, you can set FLASK_APP to the package name:

$ export FLASK_APP=mypkg
> set FLASK_APP=mypkg
> $env:FLASK_APP="mypkg"

However, if the application instance or application factory function store in mypkg/myapp.py, you will need to set FLASK_APP to:

$ export FLASK_APP=mypkg.myapp
> set FLASK_APP=mypkg.myapp
> $env:FLASK_APP="mypkg.myapp"

See Application Discovery for more details.

If you want to make the application restart whenever the code changes, you can enable reloader with --reload option:

$ flask run --reload

Tip

Install watchdog for a better performance for the application reloader:

$ pip3 install watchdog
> pip install watchdog

We highly recommend enabling "debug mode" when developing Flask application. See the note below for the details.

Enable the debug mode with FLASK_ENV

Flask can automatically restart and reload the application when code changes and display useful debug information for errors. To enable these features in your Flask application, we will need to set the environment variable FLASK_ENV to development:

$ export FLASK_ENV=development
> set FLASK_ENV=development
> $env:FLASK_APP="development"

See Debug Mode for more details.

Manage environment variables with python-dotenv

Manually setting environment is a bit inconvenient since the variable only lives in the current terminal session. You have to set it every time you reopen the terminal or reboot the computer. That's why we need to use python-dotenv, and Flask also has special support for it.

Install python-dotenv with pip:

$ pip3 install python-dotenv
> pip install python-dotenv

Now we can store environment variables in .env files. Flask-related environment variables should keep in a file called .flaskenv:

# save as .flaskenv
FLASK_APP=hello
FLASK_ENV=development

While the secrets values should save in the .env file:

# save as .env
SECRET_KEY=some-random-string
DATABASE_URL=your-database-url
FOO_APP_KEY=some-app-key

Warning

Since the .env contains sensitive information, do not commit it into the Git history. Be sure to ignore it by adding the file name into .gitignore.

In the application, now we can read these variables via os.getenv(key, default_value):

import os

from apiflask import APIFlask

app = APIFlask(__name__)
app.secret_key = os.getenv('SECRET_KEY')

Any flask command will read environment variables set by .flaskenv and .env. Now when you run flask run, Flask will read the value of FLASK_APP and FLASK_ENV in .flaskenv file to find the app instance from given import path and enable the debug mode:

$ flask run
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 101-750-099
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

See Environment Variables From dotenv for more details.

Interactive API documentation

Once you have created the app instance, the interactive API documentation will be available at http://localhost:5000/docs and http://localhost:5000/redoc. On top of that, the OpenAPI spec file is available at http://localhost:5000/openapi.json.

If you want to preview the spec or save the spec to a local file, use the flask spec command.

You can refresh the documentation whenever you added a new route or added the input and output definition for the view function in the following sections.

Create a route with route decorators

To create a view function, you can do exactly what you did with Flask:

from apiflask import APIFlask

app = APIFlask(__name__)


@app.route('/')
def index():
    return {'message': 'hello'}


@app.route('/pets/<int:pet_id>')
def get_pet(pet_id):
    return {'message': 'OK'}


@app.route('/pets')
def get_pets():
    return {'message': 'OK'}


@app.route('/pets', methods=['POST'])
def create_pet():
    return {'message': 'created'}, 201


@app.route('/pets/<int:pet_id>', methods=['PUT'])
def update_pet(pet_id):
    return {'name': 'updated'}


@app.route('/pets/<int:pet_id>', methods=['DELETE'])
def delete_pet(pet_id):
    return '', 204

However, with APIFlask, instead of setting methods argument for each route, you can also use the following shortcuts decorators:

  • app.get(): register a route that only accepts GET request.
  • app.post(): register a route that only accepts POST request.
  • app.put(): register a route that only accepts PUT request.
  • app.patch(): register a route that only accepts PATCH request.
  • app.delete(): register a route that only accepts DELETE request.

Here is the same example with the route shortcuts:

from apiflask import APIFlask

app = APIFlask(__name__)


@app.get('/')
def index():
    return {'message': 'hello'}


@app.get('/pets/<int:pet_id>')
def get_pet(pet_id):
    return {'message': 'OK'}


@app.get('/pets')
def get_pets():
    return {'message': 'OK'}


@app.post('/pets')
def create_pet():
    return {'message': 'created'}, 201


@app.put('/pets/<int:pet_id>')
def update_pet(pet_id):
    return {'message': 'updated'}


@app.delete('/pets/<int:pet_id>')
def delete_pet(pet_id):
    return '', 204

Handling multiple HTTP methods in one view function

You can't pass the methods argument to route shortcuts. If you want the view function to accept multiple HTTP methods, you will need to use the app.route() decorator to pass the methods argument:

@app.route('/', methods=['GET', 'POST'])
def index():
    return {'message': 'hello'}

Or you can do something like this:

@app.get('/')
@app.post('/')
def index():
    return {'message': 'hello'}

By the way, you can mix the use of app.route() with the shortcuts in your application.

Use @input to validate and deserialize request data

To validate and deserialize a request body or request query parameters, we need to create a resource schema class first. Think of it as a way to describe the valid incoming data. If you already familiar with marshmallow, then you already know how to write a resource schema.

Here is a simple input schema for a Pet input resource:

from apiflask import Schema
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf


class PetInSchema(Schema):
    name = String(required=True, validate=Length(0, 10))
    category = String(required=True, validate=OneOf(['dog', 'cat']))

Tip

See Schema and Fields chapter (WIP) for the details of how to write a schema and the examples for all the fields and validators.

A schema class should inherit the apiflask.Schema class:

from apiflask import Schema
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf


class PetInSchema(Schema):
    name = String(required=True, validate=Length(0, 10))
    category = String(required=True, validate=OneOf(['dog', 'cat']))

fields are represented with field classes in apiflask.fields:

from apiflask import Schema
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf


class PetInSchema(Schema):
    name = String(required=True, validate=Length(0, 10))
    category = String(required=True, validate=OneOf(['dog', 'cat']))

To validate a field with a specific rule, you can pass a validator or a list of validators (import them from apiflask.validators) to the validate argument of the field class:

from apiflask import Schema
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf


class PetInSchema(Schema):
    name = String(required=True, validate=Length(0, 10))
    category = String(required=True, validate=OneOf(['dog', 'cat']))

Tip

Notice we mark the field as a required field with the required parameter. If you want to set a default value for an input field when is missing in the input data, you can use the load_default parameter:

name = String(load_default='default name')

With this schema, we declare that the input request body should appear in the following format:

{
    "name": "the name of the pet",
    "category": "the category of the pet: one of dog and cat"
}

Now let's add it to the view function which used to create a new pet:

from apiflask import APIFlask, Schema, input
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf

app = APIFlask(__name__)


class PetInSchema(Schema):
    name = String(required=True, validate=Length(0, 10))
    category = String(required=True, validate=OneOf(['dog', 'cat']))


@app.post('/pets')
@input(PetInSchema)
def create_pet(data):
    print(data)
    return {'message': 'created'}, 201

You just need to pass the schema class to the @input decorator. When a request was received, APIFlask will validate the request body against the schema.

If the validation passed, the data will inject into the view function as a positional argument in the form of dict. Otherwise, an error response with the detail of the validation result will be returned.

In the example above, I use the name data to accept the input data dict. You can change the argument name to whatever you like. Since this is a dict, you can do something like this to create an ORM model instance:

@app.post('/pets')
@input(PetInSchema)
@output(PetOutSchema)
def create_pet(pet_id, data):
    pet = Pet(**data)
    return pet

or update an ORM model class instance like this:

@app.patch('/pets/<int:pet_id>')
@input(PetInSchema)
@output(PetOutSchema)
def update_pet(pet_id, data):
    pet = Pet.query.get(pet_id)
    for attr, value in data.items():
        setattr(pet, attr, value)
    return pet

If you want to mark the input with a different location, you can pass a location argument for @input() decorator, the value can be:

  • Request JSON body: 'json' (default)
  • Upload files: 'files'
  • Form data: 'form'
  • Cookies: 'cookies'
  • HTTP headers: 'headers'
  • Query string: 'query' (same as 'querystring')

Warning

Be sure to put the @input decorator under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Use @output to format response data

Similarly, we can define a schema for output data with @output decorator. Here is an example:

from apiflask.fields import String, Integer


class PetOutSchema(Schema):
    id = Integer()
    name = String()
    category = String()

Since APIFlask will not validate the output data, we only need to list all the field for the output schema.

Tip

You can set a default value for output field with the dump_default argument:

name = String(dump_default='default name')

Now add it to the view function which used to get a pet resource:

from apiflask import APIFlask, output
from apiflask.fields import String, Integer

app = APIFlask(__name__)


class PetOutSchema(Schema):
    id = Integer()
    name = String()
    category = String()


@app.get('/pets/<int:pet_id>')
@output(PetOutSchema)
def get_pet(pet_id):
    return {
        'name': 'Coco',
        'category: 'dog'
    }

The default status code for output response is 200, you can set a different status code with the status_code argument:

@app.post('/pets')
@input(PetInSchema)
@output(PetOutSchema, status_code=201)
def create_pet(data)
    data['id'] = 2
    return data

Or just:

@output(PetOutSchema, 201)

If you want to return a 204 response, you can use the EmptySchema from apiflask.schemas:

from apiflask.schemas import EmptySchema


@app.delete('/pets/<int:pet_id>')
@output(EmptySchema, 204)
def delete_pet(pet_id):
    return ''

From version 0.4.0, you can use a empty dict to represent empty schema:

@app.delete('/pets/<int:pet_id>')
@output({}, 204)
def delete_pet(pet_id):
    return ''

The @output decorator can only use once

You can only define one main success response for your view function, which means you can only use one @output decorator. If you want to add more alternative responses for a view in the OpenAPI spec, you can use the @doc decorator and pass a list to the responses parameter. For example:

@app.put('/pets/<int:pet_id>')
@input(PetInSchema)
@output(PetOutSchema)  # 200
@doc(responses=[204, 404])
def update_pet(pet_id, data):
    pass

Warning

Be sure to put the @output decorator under the routes decorators (i.e., app.route, app.get, app.post, etc.).

The return value of the view function

When you are using a @output(schema) decorator, you should return a dict or object that matches the schema you passed. For example, here is your schema:

from apiflask import Schema
from apiflask.fields import String, Integer


class PetOutSchema(Schema):
    id = Integer()
    name = String()
    category = String()

Now you can return a dict:

@app.get('/pets/<int:pet_id>')
@output(PetOutSchema)
def get_pet(pet_id):
    return {
        'id': 1,
        'name': 'Coco',
        'category: 'dog'
    }

or you can return an ORM model instance directly:

@app.get('/pets/<int:pet_id>')
@output(PetOutSchema)
def get_pet(pet_id):
    pet = Pet.query.get(pet_id)
    return pet

Notice your ORM model class should have the fields defined in the schema class:

class Pet(Model):
    id = Integer()
    name = String()
    category = String()

What if I want to use a different external field name?

For example, in your ORM model class, you have a phone field that store the user's phone number:

class User(Model):
    phone = String()

Now you want to output the field with the name phone_number, then you can use data_key to declare the actual key name to dump to:

class UserOutSchema(Schema):
    phone = String(data_key='phone_number')

This schema will generate something like {'phone_number': ...}.

Similarly, you can tell APIFlask to load from different key in input schema:

class UserInSchema(Schema):
    phone = String(data_key='phone_number')

This schema expects the user input is something like {'phone_number': ...}.

The default status code is 200, if you want to use a different status code, you can pass a status_code argument in the @output decorator:

@app.post('/pets')
@input(PetInSchema)
@output(PetOutSchema, 201)
def create_pet(data)
    # ...
    return pet

You don't need to return the same status code in the end of the view function (i.e., return data, 201):

@app.post('/pets')
@input(PetInSchema)
@output(PetOutSchema, 201)
def create_pet(data)
    # ...
    # equals to:
    # return pet, 201
    return pet

When you want to pass a header dict, you can pass the dict as the second element of the return tuple:

@app.post('/pets')
@input(PetInSchema)
@output(PetOutSchema, 201)
def create_pet(data)
    # ...
    # equals to:
    # return pet, 201, {'FOO': 'bar'}
    return pet, {'FOO': 'bar'}

Tips

Be sure to always set the status_code argument in @output when you want to use a non-200 status code. If there is a mismatch, the status_code passed in @output will be used in OpenAPI spec, while the actual response will use the status code you returned at the end of the view function.

The OpenAPI generating support and the doc decorator

APIFlask provides automatic OpenAPI spec generating support, while also allows you to customize the spec:

  • Most of the fields of the info object and top-level field of OpenAPI objct are accessible with configuration variables.
  • The tag object, Operation summary and description will generated from the blueprint name, the view function name and docstring.
  • You can register a spec processor function to process the spec.
  • requestBody and responses fields can be set with the input and output decorator.
  • Other operation fields can be set with the doc decorator:
from apiflask import APIFlask, doc

app = APIFlask(__name__)


@app.get('/hello')
@doc(summary='Say hello', description='Some description for the /hello')
def hello():
    return 'Hello'

See Use the doc decorator for more details about OpenAPI genenrating and the usage of the doc decorator.

Warning

Be sure to put the @doc decorator under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Use @auth_required to protect your views

You can use @auth_required to protect a view with provided authentication settings:

from apiflask import APIFlask, HTTPTokenAuth, auth_required

app = APIFlask(__name__)
auth = HTTPTokenAuth()


@app.get('/')
@auth_required(auth)
def hello():
    return 'Hello'!

See Flask-HTTPAuth's documentation for more details (The chapter of authentication support will be added soon).

Warning

Be sure to put the @auth_required decorator under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Use class-based views

Version >= 0.5.0

This feature was added in the version 0.5.0.

You can create a group of routes under the same URL rule with the MethodView class. Here is a simple example:

from flask.views import MethodView
from apiflask import APIFlask

app = APIFlask(__name__)


@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):

    def get(self, pet_id):
        return {'message': 'OK'}

    def delete(self, pet_id):
        return '', 204

When creating a view class, it needs to inherit from the MethodView class, since APIFlask can only generate OpenAPI spec for MethodView-based view classes.:

from flask.views import MethodView

@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):
    # ...

APIFlask supports to use the route decorator on view classes as a shortcut for add_url_rule:

@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):
    # ...

Tips

If the endpoint argument isn't provided, the class name will be used as endpoint. You don't need to pass a methods argument, since Flask will handle it for you.

Now, you can define view methods for each HTTP method, use the (HTTP) method name as method name:

@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):

    def get(self, pet_id):  # triggered by GET request
        return {'message': 'OK'}

    def post(self, pet_id):  # triggered by POST request
        return {'message': 'OK'}

    def put(self, pet_id):  # triggered by PUT request
        return {'message': 'OK'}

    def delete(self, pet_id):  # triggered by DELETE request
        return '', 204

    def patch(self, pet_id):  # triggered by PATCH request
        return {'message': 'OK'}

With the example application above, when the user sends a GET request to /pets/<int:pet_id>, the get() method of the Pet class will be called, and so on for the others.

From version 0.10.0, you can also use the add_url_rule method to register view classes:

class Pet(MethodView):
    # ...

app.add_url_rule('/pets/<int:pet_id>', view_func=Pet.as_view('pet'))

You still don't need to set the methods, but you will need if you want to register multiple rules for one view classes based on the methods, this can only be achieved with add_url_rule. For example, the post method you created above normally has a different URL rule than the others:

class Pet(MethodView):
    # ...

pet_view = Pet.as_view('pet')
app.add_url_rule('/pets/<int:pet_id>', view_func=pet_view, methods=['GET', 'PUT', 'DELETE', 'PATCH'])
app.add_url_rule('/pets', view_func=pet_view, methods=['POST'])

When you use decorators like @input, @output, be sure to use it on method instead of class:

@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):

    @output(PetOutSchema)
    @doc(summary='Get a Pet')
    def get(self, pet_id):
        # ...

    @auth_required(auth)
    @input(PetInSchema)
    @output(PetOutSchema)
    def put(self, pet_id, data):
        # ...

    @input(PetInSchema(partial=True))
    @output(PetOutSchema)
    def patch(self, pet_id, data):
        # ...

If you want to apply a decorator for all methods, instead of repeat yourself, you can pass the decorator to the class attribute decorators, it accepts a list of decorators:

@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):

    decorators = [auth_required(auth), doc(responses=[404])]

    @output(PetOutSchema)
    @doc(summary='Get a Pet')
    def get(self, pet_id):
        # ...

    @auth_required(auth)
    @input(PetInSchema)
    @output(PetOutSchema)
    def put(self, pet_id, data):
        # ...

    @input(PetInSchema(partial=True))
    @output(PetOutSchema)
    def patch(self, pet_id, data):
        # ...

Use abort() to return an error response

Similar to Flask's abort, but abort from APIFlask will return a JSON response.

Example:

from apiflask import APIFlask, abort

app = APIFlask(__name__)

@app.get('/<name>')
def hello(name):
    if name == 'Foo':
        abort(404, 'This man is missing.')
    return {'hello': name}

Tip

When app.json_errors is True (default), Flask's abort will also return JSON error response.

You can also raise an HTTPError exception to return an error response:

from apiflask import APIFlask, HTTPError

app = APIFlask(__name__)

@app.get('/<name>')
def hello(name):
    if name == 'Foo':
        raise HTTPError(404, 'This man is missing.')
    return {'hello': name}

The abort() and HTTPError accept the following arguments:

  • status_code: The status code of the error (4XX and 5xx).
  • message: The simple description of the error. If not provided, the reason phrase of the status code will be used.
  • detail: The detailed information of the error, it can be used to provide additional information such as custom error code, documentation URL, etc.
  • headers: A dict of headers used in the error response.

Warning

The function abort_json() was renamed to abort() in the version 0.4.0.

Overview of the apiflask package

In the end, let's unpack the whole apiflask package to check out what it shipped with:

  • APIFlask: A class used to create an application instance (A wrapper for Flask's Flask class).
  • APIBlueprint: A class used to create a blueprint instance (A wrapper for Flask's Blueprint class)..
  • @input: A decorator used to validate the input/request data from request body, query string, etc.
  • @output: A decorator used to format the response.
  • @auth_required: A decorator used to protect a view from unauthenticated users.
  • @doc: A decorator used to set up the OpenAPI spec for view functions.
  • abort(): A function used to abort the request handling process and return an error response. The JSON version of Flask's flask.abort() function.
  • HTTPError: An exception used to return error response (used by abort()).
  • HTTPBasicAuth: A class used to create an auth instance.
  • HTTPTokenAuth: A class used to create an auth instance.
  • Schema: A base class for resource schemas (Will be a wrapper for marshmallow's Schema).
  • fields: A module contains all the fields (from marshmallow).
  • validators: A module contains all the field validators (from marshmallow).
  • app.get(): A decorator used to register a route that only accepts GET request.
  • app.post(): A decorator used to register a route that only accepts POST request.
  • app.put(): A decorator used to register a route that only accepts PUT request.
  • app.patch(): A decorator used to register a route that only accepts PATCH request.
  • app.delete(): A decorator used to register a route that only accepts DELETE request.
  • app.route(): A decorator used to register a route. It accepts a methods parameter to specify a list of accepted methods, default to GET only. It can also be used on the MethodView-based view class.
  • $ flask run: A command to output the spec to stdout or a file.

You can learn the details of these APIs in the API reference, or you can continue to read the following chapters.