Slide 1

Slide 1 text

GraphQL-first Django DjangoCon Europe 2020 Marcin Gębala @maarcingebala 1

Slide 2

Slide 2 text

About me » Lead Developer at Saleor Commerce » Python, Django and GraphQL » Based in Wrocław, Poland @maarcingebala 2

Slide 3

Slide 3 text

Saleor Saleor is an open-source headless e-commerce platform. » GraphQL API built with Django and Graphene (with over 350 operations) » Dashboard - management app for store owners (Typescript + React) » Storefront - template of an online shop (Typescript + React) saleor.io github.com/mirumee/saleor @maarcingebala 3

Slide 4

Slide 4 text

What is GraphQL? @maarcingebala 4

Slide 5

Slide 5 text

Open-source data query and manipulation language for APIs @maarcingebala 5

Slide 6

Slide 6 text

@maarcingebala 6

Slide 7

Slide 7 text

Features » Fetching only data that is needed; the client decides what data to fetch » Three types of operations: queries, mutations, subscriptions » Combining multiple resources in a single request » Strong typing » ✨ Developer experience: interactive IDEs, code generation, API mocking ✨ @maarcingebala 7

Slide 8

Slide 8 text

Schema type Query { products: [Product] } type Mutation { createProduct(input: ProductInput): Product } type Product { name: String! description: String createdAt: Date! categories: [Category] } input ProductInput { name: String! description: String categories: [ID] } » Definition of the API » Contract between frontend and backend » Schema-first vs. code- first approach @maarcingebala 8

Slide 9

Slide 9 text

GraphQL-first Django @maarcingebala 9

Slide 10

Slide 10 text

Graphene High-level framework for building GraphQL APIs in Python. » Rich ecosystem of libraries and integrations (Django, Flask, SQLAlchemy, Mongo) » Code-first approach - generating GraphQL schema from Python classes » The best Django integration of all GraphQL libraries in Python graphene-python.org github.com/graphql-python/graphene @maarcingebala 10

Slide 11

Slide 11 text

Project structure » types.py - app's types; mapping models to GraphQL types » mutations.py - app's create/ update/delete operations » dataloaders.py - efficient data loading for queries and types » schema.py - schema of a single app » root schema.py - combine schema of all apps @maarcingebala 11

Slide 12

Slide 12 text

Urls Single urls.py file with one view: # urls.py from django.urls import path from graphene_django.views import GraphQLView urlpatterns = [ path("graphql", GraphQLView.as_view(graphiql=True)), ] @maarcingebala 12

Slide 13

Slide 13 text

Types # types.py from graphene_django import DjangoObjectType class Product(DjangoObjectType): price = graphene.Field(Money, description="Price of a product.") class Meta: description = "Represents a product." model = models.Product only_fields = [ "name", "description", "is_published", "updated_at", ] @staticmethod def resolve_price(root: models.Product, *_): return Money(amount=root.price, currency=settings.DEFAULT_CURRENCY) @maarcingebala 13

Slide 14

Slide 14 text

Queries # schema.py class ProductQueries(graphene.ObjectType): products = graphene.List(types.Product) @staticmethod def resolve_products(*_): return models.Product.objects.all() @maarcingebala 14

Slide 15

Slide 15 text

Mutations # mutations.py class ProductCreate(graphene.Mutation): product = graphene.Field(types.Product) errors = graphene.List(Error, required=True, default_value=[]) class Arguments: input = ProductCreateInput(required=True) @classmethod def mutate(cls, root, info, input): product = models.Product(**input) try: product.full_clean() except ValidationError as e: errors = validation_error_to_error_type(e) return ProductCreate(errors=errors) product.save() return ProductCreate(product=product) @maarcingebala 15

Slide 16

Slide 16 text

Form-based mutations Form mutations allow for generating mutations from forms. from graphene_django.forms.mutation import DjangoModelFormMutation class ProductCreateMutation(DjangoModelFormMutation): class Meta: form_class = ProductForm input_field_name = "input" return_field_name = "product" @maarcingebala 16

Slide 17

Slide 17 text

Authentication JSON Web Token authentication is provided by django-graphql-jwt package: mutation { tokenCreate(email: "[email protected]", password: "secret") { token refreshToken user { id email } } } Authenticating requests with the Authorization header: { "Authorization": "JWT jwt-token-value" } @maarcingebala 17

Slide 18

Slide 18 text

Permissions django-graphql-jwt also provides decorators to restrict access to particular fields in the schema. # types.py from graphql_jwt.decorators import permission_required class Product(DjangoObjectType): revenue = graphene.Field(Money) @permission_required("product.manage_products") def resolve_revenue(root: models.Product, *_): return get_product_revenue(root) @maarcingebala 18

Slide 19

Slide 19 text

N+1 problem GraphQL query to return a list of products with categories, category is a foreign key in the Product model: { products { name category { name } } } Database queries: SELECT * FROM "products_product"; SELECT * FROM "products_category" WHERE "products_category"."id" = 1; SELECT * FROM "products_category" WHERE "products_category"."id" = 2; SELECT * FROM "products_category" WHERE "products_category"."id" = 3; ... SELECT * FROM "products_category" WHERE "products_category"."id" = N; @maarcingebala 19

Slide 20

Slide 20 text

Data loaders Usage: class Product(DjangoObjectType): category = graphene.Field(Category, description="Product's category") @staticmethod def resolve_category(root: models.Product, info, *_): return CategoryByIdLoader(info.context).load(root.category_id) Loading a foreign key relation: from promise import Promise from promise.dataloader import DataLoader class CategoryByIdLoader(DataLoader): def batch_load_fn(self, keys): categories = models.Category.objects.in_bulk(keys) results = [categories.get(category_id) for category_id in keys] return Promise.resolve(results) @maarcingebala 20

Slide 21

Slide 21 text

Inspect DB queries with django-debug-toolbar and django-graphiql-debug-toolbar extension. @maarcingebala 21

Slide 22

Slide 22 text

Testing Testing API operations with PyTest: PRODUCT_CREATE_MUTATION = """ mutation ProductCreate($slug: String, $name: String) { productCreate(input: { slug: $slug, name: $title }) { product { slug name } errors { message } } } """ def test_product_create_mutation(api_client): name = "T-shirt" slug = "t-shirt" variables = {"name": name, "slug": slug} response = api_client.post_graphql(PRODUCT_CREATE_MUTATION, variables) content = get_graphql_content(response) data = content["data"]["productCreate"] assert data["errors"] == [] assert data["product"]["name"] == name assert data["product"]["slug"] == slug @maarcingebala 22

Slide 23

Slide 23 text

Real-time queries GraphQL provides support for real-time queries with subscriptions: @maarcingebala 23

Slide 24

Slide 24 text

Real-time queries » Graphene-Django doesn't support subscriptions yet; there is no ASGI view/consumer to process WebSocket requests » Ongoing work on adding subscriptions support in Graphene v3 » Third-party libraries that add subscriptions support: » github.com/jaydenwindle/graphene-subscriptions » github.com/graphql-python/graphql-ws @maarcingebala 24

Slide 25

Slide 25 text

Is Django a good choice for a GraphQL server? @maarcingebala 25

Slide 26

Slide 26 text

Pros » Django = productivity. ORM and Graphene's code- first approach lets you progress fast » Familiar concepts: object types, form-based mutations, declarative style » Easy to add GraphQL API to an existing Django app » Example: Saleor GraphQL API built entirely in Django + Graphene @maarcingebala 26

Slide 27

Slide 27 text

Cons » A fully-fledged server requires many additional libraries, which are not always well maintained » Lack of good learning resources » Uncertain roadmap of Graphene @maarcingebala 27

Slide 28

Slide 28 text

Other libraries Ariadne - schema-first library for implementing GraphQL servers in Python. » fully-asynchronous; support for subscriptions » provides WSGI and ASGI views for Django » active community on Spectrum ariadnegraphql.org @maarcingebala 28

Slide 29

Slide 29 text

[email protected] @maarcingebala Thank you! @maarcingebala 29

Slide 30

Slide 30 text

Resources » Books: » "Learning GraphQL" by Eve Porcello, Alex Banks » "Production Ready GraphQL" by Marc-Andre Giroux » Tutorials: » Fullstack Apollo tutorial - apollographql.com/docs/tutorial/ introduction » How to GraphQL - howtographql.com » Example implementations: » Saleor GraphQL API: github.com/mirumee/saleor @maarcingebala 30