Skip to content

Basic Usage

This chapter will cover the basic usage of APIFlask.

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

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

from apiflask import APIFlask

app = APIFlask(__name__)


@app.route('/')
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 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

I highly recommend enabling "debug mode" when developing Flask application, see the note below for the details.

Enabling the debug mode

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"

For a proper Flask application setup, we normally store the environment variables into a file called .flaskenv (which is used to store Flask-specific environment variables):

# save as .flaskenv
FLASK_ENV=development

then install python-dotenv:

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

Now when you run flask run, the application starts in 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)

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 will be available at http://localhost:5000/openapi.json.

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

With APIFlask, instead of setting methods argument for each route, you can 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.

This 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

Tip

If you want the view function to accept multiple methods, you still need to use app.route() decorator. 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 required argument. If you want to set a default value for an input field when is missing in the input data, you can use missing argument:

name = String(missing='default name')

With this schema, we announce the input request body should 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 be injected 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 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 formatting 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 default argument:

name = String(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 used once

You can only defined 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 responses argument. 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 field name in the schema?

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 argument to declare the actual key name to load from:

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

The default status code is 200, if you want to use a different status code, you can pass a status_code arguent 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 in the end of the view function.

Use @doc to set up OpenAPI Spec

The @doc decorator can be used to set up the OpenAPI Spec for view functions:

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'!

As default, APIFlask will use the name of the view function as the value of summary. If your view function is named with get_pet, then the summary will be "Get Pet".

If the view function has docstring, then the first line of the docstring will be used as the summary, the lines after the empty line of the docstring will be used as the description.

The precedence of summary setting

@doc(summary='blah') > the first line of docstring > the view function name

Hence the example above is equals to:

from apiflask import APIFlask

app = APIFlask(__name__)


@app.get('/hello')
def hello():
    """Say hello

    Some description for the /hello
    """
    return 'Hello'

Here are the other arguments for the @doc argument:

  • tag: The tag or tag list of this endpoint, map the tags you passed in the app.tags attribute. You can pass a list of tag names or just a single tag name string. If app.tags is not set, the blueprint name will be used as the tag name.
  • responses: The other responses for this view function, accepts a dict in a format of {404: 'Not Found'} or a list of status code ([404, 418]).
  • deprecated: Flag this endpoint as deprecated in API docs. Defaults to False.
  • hide: Hide this endpoint in API docs. Defaults to False.

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 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':
        rasie 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 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.

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