Quickstart

It’s time to write your first REST API. This guide assumes you have a working understanding of Flask, and that you have already installed both Flask and Flask-REST-JSONAPI. If not, then follow the steps in the Installation section.

In this section you will learn basic usage of Flask-REST-JSONAPI around a small tutorial that use the SQLAlchemy data layer. This tutorial show you an example of a person and his computers.

First example

An example of Flask-REST-JSONAPI API looks like this:

# -*- coding: utf-8 -*-

from flask import Flask
from flask_rest_jsonapi import Api, ResourceDetail, ResourceList, ResourceRelationship
from flask_rest_jsonapi.exceptions import ObjectNotFound
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from marshmallow_jsonapi.flask import Schema, Relationship
from marshmallow_jsonapi import fields

# Create the Flask application
app = Flask(__name__)
app.config['DEBUG'] = True


# Initialize SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)


# Create data storage
class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    email = db.Column(db.String)
    birth_date = db.Column(db.Date)
    password = db.Column(db.String)


class Computer(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    serial = db.Column(db.String)
    person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
    person = db.relationship('Person', backref=db.backref('computers'))

db.create_all()


# Create logical data abstraction (same as data storage for this first example)
class PersonSchema(Schema):
    class Meta:
        type_ = 'person'
        self_view = 'person_detail'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'person_list'

    id = fields.Integer(as_string=True, dump_only=True)
    name = fields.Str(required=True, load_only=True)
    email = fields.Email(load_only=True)
    birth_date = fields.Date()
    display_name = fields.Function(lambda obj: "{} <{}>".format(obj.name.upper(), obj.email))
    computers = Relationship(self_view='person_computers',
                             self_view_kwargs={'id': '<id>'},
                             related_view='computer_list',
                             related_view_kwargs={'id': '<id>'},
                             many=True,
                             schema='ComputerSchema',
                             type_='computer')


class ComputerSchema(Schema):
    class Meta:
        type_ = 'computer'
        self_view = 'computer_detail'
        self_view_kwargs = {'id': '<id>'}

    id = fields.Integer(as_string=True, dump_only=True)
    serial = fields.Str(required=True)
    owner = Relationship(attribute='person',
                         self_view='computer_person',
                         self_view_kwargs={'id': '<id>'},
                         related_view='person_detail',
                         related_view_kwargs={'computer_id': '<id>'},
                         schema='PersonSchema',
                         type_='person')


# Create resource managers
class PersonList(ResourceList):
    schema = PersonSchema
    data_layer = {'session': db.session,
                  'model': Person}


class PersonDetail(ResourceDetail):
    def before_get_object(self, view_kwargs):
        if view_kwargs.get('computer_id') is not None:
            try:
                computer = self.session.query(Computer).filter_by(id=view_kwargs['computer_id']).one()
            except NoResultFound:
                raise ObjectNotFound({'parameter': 'computer_id'},
                                     "Computer: {} not found".format(view_kwargs['computer_id']))
            else:
                if computer.person is not None:
                    view_kwargs['id'] = computer.person.id
                else:
                    view_kwargs['id'] = None

    schema = PersonSchema
    data_layer = {'session': db.session,
                  'model': Person,
                  'methods': {'before_get_object': before_get_object}}


class PersonRelationship(ResourceRelationship):
    schema = PersonSchema
    data_layer = {'session': db.session,
                  'model': Person}


class ComputerList(ResourceList):
    def query(self, view_kwargs):
        query_ = self.session.query(Computer)
        if view_kwargs.get('id') is not None:
            try:
                self.session.query(Person).filter_by(id=view_kwargs['id']).one()
            except NoResultFound:
                raise ObjectNotFound({'parameter': 'id'}, "Person: {} not found".format(view_kwargs['id']))
            else:
                query_ = query_.join(Person).filter(Person.id == view_kwargs['id'])
        return query_

    def before_create_object(self, data, view_kwargs):
        if view_kwargs.get('id') is not None:
            person = self.session.query(Person).filter_by(id=view_kwargs['id']).one()
            data['person_id'] = person.id

    schema = ComputerSchema
    data_layer = {'session': db.session,
                  'model': Computer,
                  'methods': {'query': query,
                              'before_create_object': before_create_object}}


class ComputerDetail(ResourceDetail):
    schema = ComputerSchema
    data_layer = {'session': db.session,
                  'model': Computer}


class ComputerRelationship(ResourceRelationship):
    schema = ComputerSchema
    data_layer = {'session': db.session,
                  'model': Computer}


# Create endpoints
api = Api(app)
api.route(PersonList, 'person_list', '/persons')
api.route(PersonDetail, 'person_detail', '/persons/<int:id>', '/computers/<int:computer_id>/owner')
api.route(PersonRelationship, 'person_computers', '/persons/<int:id>/relationships/computers')
api.route(ComputerList, 'computer_list', '/computers', '/persons/<int:id>/computers')
api.route(ComputerDetail, 'computer_detail', '/computers/<int:id>')
api.route(ComputerRelationship, 'computer_person', '/computers/<int:id>/relationships/owner')

if __name__ == '__main__':
    # Start application
    app.run(debug=True)

This example provides this api:

url method endpoint action
/persons GET person_list Retrieve a collection of persons
/persons POST person_list Create a person
/persons/<int:id> GET person_detail Retrieve details of a person
/persons/<int:id> PATCH person_detail Update a person
/persons/<int:id> DELETE person_detail Delete a person
/persons/<int:id>/computers GET computer_list Retrieve a collection computers related to a person
/persons/<int:id>/computers POST computer_list Create a computer related to a person
/persons/<int:id>/relationship/computers GET person_computers Retrieve relationships between a person and computers
/persons/<int:id>/relationship/computers POST person_computers Create relationships between a person and computers
/persons/<int:id>/relationship/computers PATCH person_computers Update relationships between a person and computers
/persons/<int:id>/relationship/computers DELETE person_computers Delete relationships between a person and computers
/computers GET computer_list Retrieve a collection of computers
/computers POST computer_list Create a computer
/computers/<int:id> GET computer_detail Retrieve details of a computer
/computers/<int:id> PATCH computer_detail Update a computer
/computers/<int:id> DELETE computer_detail Delete a computer
/computers/<int:id>/owner GET person_detail Retrieve details of the owner of a computer
/computers/<int:id>/owner PATCH person_detail Update the owner of a computer
/computers/<int:id>/owner DELETE person_detail Delete the owner of a computer
/computers/<int:id>/relationship/owner GET person_computers Retrieve relationships between a person and computers
/computers/<int:id>/relationship/owner POST person_computers Create relationships between a person and computers
/computers/<int:id>/relationship/owner PATCH person_computers Update relationships between a person and computers
/computers/<int:id>/relationship/owner DELETE person_computers Delete relationships between a person and computers

Warning

In this example, I use Flask-SQLAlchemy so you have to install it before to run the example.

$ pip install flask_sqlalchemy

Save this as api.py and run it using your Python interpreter. Note that we’ve enabled Flask debugging mode to provide code reloading and better error messages.

$ python api.py
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader

Warning

Debug mode should never be used in a production environment!

Classical CRUD operations

Create object

Request:

POST /computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "computer",
    "attributes": {
      "serial": "Amstrad"
    }
  }
}

Response:

HTTP/1.1 201 Created
Content-Type: application/vnd.api+json

{
  "data": {
    "type": "computer",
    "id": "1",
    "attributes": {
      "serial": "Amstrad"
    },
    "relationships": {
      "owner": {
        "links": {
          "related": "/computers/1/owner",
          "self": "/computers/1/relationships/owner"
        }
      }
    },
    "links": {
      "self": "/computers/1"
    }
  },
  "links": {
    "self": "/computers/1"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

List objects

Request:

GET /computers HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "type": "computer",
      "id": "1",
      "attributes": {
        "serial": "Amstrad"
      },
      "relationships": {
        "owner": {
          "links": {
            "related": "/computers/1/owner",
            "self": "/computers/1/relationships/owner"
          }
        }
      },
      "links": {
        "self": "/computers/1"
      }
    }
  ],
  "meta": {
    "count": 1
  },
  "links": {
    "self": "/computers"
  },
  "jsonapi": {
    "version": "1.0"
  },
}

Update object

Request:

PATCH /computers/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "computer",
    "id": "1",
    "attributes": {
      "serial": "Amstrad 2"
    }
  }
}

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "type": "computer",
    "id": "1",
    "attributes": {
      "serial": "Amstrad 2"
    },
    "relationships": {
      "owner": {
        "links": {
          "related": "/computers/1/owner",
          "self": "/computers/1/relationships/owner"
        }
      }
    },
    "links": {
      "self": "/computers/1"
    }
  },
  "links": {
    "self": "/computers/1"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Delete object

Request:

DELETE /computers/1 HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "meta": {
    "message": "Object successfully deleted"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Relationships

Now let’s use relationships tools. First, create 3 computers named Halo, Nestor and Comodor like in previous example.

Done ?
Ok. So let’s continue this tutorial.

We assume that Halo has id: 2, Nestor id: 3 and Comodor has id: 4.

Update object and his relationships

Now John sell his Amstrad and buy a new computer named Nestor (id: 3). So we want to link this new computer to John. John have also made a mistake in his birth_date so let’s update this 2 things in the same time.

Request:

PATCH /persons/1?include=computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "person",
    "id": "1",
    "attributes": {
      "birth_date": "1990-10-18"
    },
    "relationships": {
      "computers": {
        "data": [
          {
            "type": "computer",
            "id": "3"
          }
        ]
      }
    }
  }
}

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "type": "person",
    "id": "1",
    "attributes": {
      "display_name": "JOHN <john@gmail.com>",
      "birth_date": "1990-10-18",
    },
    "links": {
      "self": "/persons/1"
    },
    "relationships": {
      "computers": {
        "data": [
          {
            "id": "3",
            "type": "computer"
          }
        ],
        "links": {
          "related": "/persons/1/computers",
          "self": "/persons/1/relationships/computers"
        }
      }
    },
  },
  "included": [
    {
      "type": "computer",
      "id": "3",
      "attributes": {
        "serial": "Nestor"
      },
      "relationships": {
        "owner": {
          "links": {
            "related": "/computers/3/owner",
            "self": "/computers/3/relationships/owner"
          }
        }
      },
      "links": {
        "self": "/computers/3"
      }
    }
  ],
  "links": {
    "self": "/persons/1"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Create relationship

Now John buy a new computer named Comodor so let’s link it to John.

Request:

POST /persons/1/relationships/computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": [
    {
      "type": "computer",
      "id": "4"
    }
  ]
}

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "type": "person",
    "id": "1",
    "attributes": {
      "display_name": "JOHN <john@gmail.com>",
      "birth_date": "1990-10-18"
    },
    "relationships": {
      "computers": {
        "data": [
          {
            "id": "3",
            "type": "computer"
          },
          {
            "id": "4",
            "type": "computer"
          }
        ],
        "links": {
          "related": "/persons/1/computers",
          "self": "/persons/1/relationships/computers"
        }
      }
    },
    "links": {
      "self": "/persons/1"
    }
  },
  "included": [
    {
      "type": "computer",
      "id": "3",
      "attributes": {
        "serial": "Nestor"
      },
      "relationships": {
        "owner": {
          "links": {
            "related": "/computers/3/owner",
            "self": "/computers/3/relationships/owner"
          }
        }
      },
      "links": {
        "self": "/computers/3"
      }
    },
    {
      "type": "computer",
      "id": "4",
      "attributes": {
        "serial": "Comodor"
      },
      "relationships": {
        "owner": {
          "links": {
            "related": "/computers/4/owner",
            "self": "/computers/4/relationships/owner"
          }
        }
      },
      "links": {
        "self": "/computers/4"
      }
    }
  ],
  "links": {
    "self": "/persons/1/relationships/computers"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Delete relationship

Now John sell his old Nestor computer so let’s unlink it from John.

Request:

DELETE /persons/1/relationships/computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": [
    {
      "type": "computer",
      "id": "3"
    }
  ]
}

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "type": "person",
    "id": "1",
    "attributes": {
      "display_name": "JOHN <john@gmail.com>",
      "birth_date": "1990-10-18"
    },
    "relationships": {
      "computers": {
        "data": [
          {
            "id": "4",
            "type": "computer"
          }
        ],
        "links": {
          "related": "/persons/1/computers",
          "self": "/persons/1/relationships/computers"
        }
      }
    },
    "links": {
      "self": "/persons/1"
    }
  },
  "included": [
    {
      "type": "computer",
      "id": "4",
      "attributes": {
        "serial": "Comodor"
      },
      "relationships": {
        "owner": {
          "links": {
            "related": "/computers/4/owner",
            "self": "/computers/4/relationships/owner"
          }
        }
      },
      "links": {
        "self": "/computers/4"
      }
    }
  ],
  "links": {
      "self": "/persons/1/relationships/computers"
  },
  "jsonapi": {
      "version": "1.0"
  }
}

If you want to see more examples go to JSON API 1.0 specification