How to

Accessing the request

While handling a request inside a resource, you can use self.request to access the starlette Request object.

Accessing the deserialized body

You can deserialize the body and raise validation errors by calling self.deserialize_body.

Validation errors will result in 400 HTTP responses.

Formatting exceptions

Exceptions that are raised inside handlers, are serialized as JSON:API errors. In some situations though, the handlers might not be called because the exception is handled before the framework has a chance to catch it. For this reason, registering application level exception handlers might be needed:

from starlette.applications import Starlette
from starlette_jsonapi.utils import register_jsonapi_exception_handlers

app = Starlette()
register_jsonapi_exception_handlers(app)

Client generated IDs

The JSON:API spec mentions:

A server MAY accept a client-generated ID along with a request to create a resource.

To enable client generated IDS, specify the Schema’s id field without the usual dump_only attribute that has been presented in this documentation. Doing this will make marshmallow read the id field when deserialize_body is called.

Note: Validation of the client generated ID is not provided by this framework, but the specification mentions:

An ID MUST be specified with an id key, the value of which MUST be a universally unique identifier.

If you intend to use uuid IDs, set id_mask = 'uuid' when defining the Resource class, and some validation will be handled by Starlette.

Requests with malformed IDs will likely result in 404 errors.

Top level meta objects

To include a meta object (documentation) in the top level json:api response, you can pass a dictionary meta argument when calling starlette_jsonapi.resource.BaseResource.to_response(), or starlette_jsonapi.resource.BaseRelationshipResource.to_response():

await self.to_response({'id': 123, ....}, meta={'copyright': 'FooBar'})

Versioning

Versioning can be implemented by specifying register_as on the resource class.

from marshmallow import fields
from starlette.applications import Starlette
from starlette_jsonapi.resource import BaseResource
from starlette_jsonapi.schema import JSONAPISchema

class ExampleSchema(JSONAPISchema):
    id = fields.Str(dump_only=True)
    description = fields.Str()

class ExampleResourceV1(BaseResource):
    type_ = 'examples'
    schema = ExampleSchema
    register_as = 'v1-examples'

class ExampleResourceV2(BaseResource):
    type_ = 'examples'
    schema = ExampleSchema
    register_as = 'v2-examples'

app = Starlette()
ExampleResourceV1.register_routes(app, base_path='/v1/')
ExampleResourceV2.register_routes(app, base_path='/v2/')

# both resources are now accessible without conflicts:
assert app.url_path_for('v1-examples:get_many') == '/v1/examples/'
assert app.url_path_for('v2-examples:get_many') == '/v2/examples/'

Generate OpenAPI spec

Although starlette-jsonapi does not include the Swagger UI files, a JSON of the OpenAPI representation can be generated and then passed to a Swagger UI docker container.

Example:

from apispec import APISpec
from starlette.applications import Starlette
from starlette_jsonapi.openapi import JSONAPISchemaGenerator, JSONAPIMarshmallowPlugin

schemas = JSONAPISchemaGenerator(
    APISpec(
        title='Example API',
        version='1.0',
        openapi_version='3.0.3',
        info={'description': 'An example API.'},
        plugins=[JSONAPIMarshmallowPlugin()],
    )
)

app = Starlette()

# register resources routes on app
# ...

schema_dict = schemas.get_schema(app.routes)
with open('data/schema.json', 'w') as f:
    f.write(json.dumps(schema_dict, indent=4))

Once generated, the schema can be mounted inside a docker container for the Swagger UI:

docker pull swaggerapi/swagger-ui
docker run -p 8080:8080 -e SWAGGER_JSON=/foo/schema.json -v data:/foo swaggerapi/swagger-ui