GraphQL
=======
`flarchitect` can expose SQLAlchemy models through a GraphQL API. The
:func:`flarchitect.graphql.create_schema_from_models` helper builds a Graphene
schema from your models, while :meth:`flarchitect.Architect.init_graphql`
registers a ``/graphql`` endpoint and documents it in the OpenAPI spec.
Quick start
-----------
The simplest way to enable GraphQL is to feed your models to
``create_schema_from_models`` and register the resulting schema with the
architect:
.. code-block:: python
schema = create_schema_from_models([User], db.session)
architect.init_graphql(schema=schema)
The generated schema provides CRUD-style queries and mutations for each model.
An ``all_items`` query returns every ``Item`` and accepts optional column
arguments for filtering. Pagination is supported via ``limit`` and
``offset`` arguments. ``create_item``, ``update_item`` and ``delete_item``
mutations manage individual records.
Type mapping
------------
``create_schema_from_models`` converts SQLAlchemy column types into Graphene
scalars using :data:`flarchitect.graphql.SQLA_TYPE_MAPPING`. Out of the box it
supports ``Integer``, ``String``, ``Boolean``, ``Float``, ``Date``, ``DateTime``,
``Numeric``, ``JSON`` and ``UUID`` columns. Custom or proprietary SQLAlchemy
types can be mapped by providing a ``type_mapping`` override:
.. code-block:: python
schema = create_schema_from_models(
[User], db.session, type_mapping={MyType: graphene.String}
)
Example mutations
~~~~~~~~~~~~~~~~~
``create_schema_from_models`` automatically generates ``create_
``,
``update_`` and ``delete_`` mutations. Each accepts the model's
columns as arguments with the primary key required for updates and deletions.
The examples below create, update and delete an ``Item``:
.. code-block:: graphql
mutation {
create_item(name: "Foo") {
id
name
}
}
mutation {
update_item(id: 1, name: "Bar") {
id
name
}
}
mutation {
delete_item(id: 1)
}
Example query
~~~~~~~~~~~~~
.. code-block:: graphql
query {
all_items(name: "Foo", limit: 1, offset: 0) {
id
name
}
}
Filtering on any column is supported. The following returns all ``Item``
objects with ``name`` equal to ``"Bar"``:
.. code-block:: graphql
query {
all_items(name: "Bar") {
id
name
}
}
Visit ``/graphql`` in a browser to access the interactive GraphiQL editor
served on ``GET`` requests. Programmatic clients should send HTTP ``POST``
requests with a ``query`` payload.
Advanced usage
--------------
Custom type mappings
~~~~~~~~~~~~~~~~~~~~
``flarchitect`` maps common SQLAlchemy column types to Graphene scalars via the
``SQLA_TYPE_MAPPING`` dictionary. Extend this mapping to support application
specific types:
.. code-block:: python
from datetime import datetime
import graphene
from flarchitect.graphql import SQLA_TYPE_MAPPING
SQLA_TYPE_MAPPING[datetime] = graphene.DateTime
Relationships
~~~~~~~~~~~~~
``create_schema_from_models`` automatically inspects SQLAlchemy relationships
and adds fields returning the related object types. The example below links
``Item`` to ``Category`` so a query for items can also retrieve the owning
category. Relationships are eagerly loaded using ``joinedload`` to avoid N+1
query issues.
.. code-block:: python
class Category(db.Model):
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String)
class Item(db.Model):
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String)
category_id = mapped_column(ForeignKey("category.id"))
category = relationship(Category)
``Item`` now exposes a ``category`` field and ``Category`` a ``items`` field. A
single request can retrieve nested data:
.. code-block:: graphql
query {
all_items {
name
category { name }
}
}
Filtering and pagination
~~~~~~~~~~~~~~~~~~~~~~~~
Queries accept optional ``limit`` and ``offset`` arguments to page through large
datasets. Additional arguments can be introduced to perform simple filtering:
.. code-block:: graphql
query {
all_items(name: "Foo", limit: 5, offset: 10) {
id
name
}
}
CRUD mutations
~~~~~~~~~~~~~~
``create_schema_from_models`` exposes a full set of CRUD mutations out of the
box, letting clients insert, modify and remove records without manual schema
definitions.
Tips and trade-offs
-------------------
GraphQL offers flexible queries and reduces the number of HTTP round-trips, but
it also introduces additional complexity. Responses are not cacheable by
standard HTTP mechanisms, and naïve schemas can allow very expensive queries.
Ensure resolvers validate user input and consider depth limiting or query cost
analysis for production deployments.
Further examples are available in :mod:`demo.graphql`.