Mise en place d'une architecture avec un backend Django et un frontend Vue communiquant via une API GraphQL. Les sources sont disponibles sur sourcehut.
Partie serveur : Django
Il existe deux modules pour la prise en charge de GraphQL dans Django : graphene-django et strawberry-graphql-django. Il faut donc installer les modules graphene-django
et django-cors-headers
. Ce dernier permettant de gérer les requêtes cross-origin entre les parties serveur et client.
Pour gérer les fonctionnalités avancées comme la pagination (graphql-relay) ou les filtres (django-filter), voir la documentation.
Configuration des modules dans settings.xml
:
INSTALLED_APPS = [
...
'graphene_django',
'corsheaders',
'app',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
GRAPHENE = {
'SCHEMA': 'app.schema.schema'
}
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = ("http://localhost:8080",)
Le middleware Cors
est configuré pour autoriser les requêtes provenant de localhost:8080
. Graphene
est configuré pour récupérer le schéma GraphQL dans la variable app.schema.schema
.
Définition des routes dans urls.py
:
from graphene_django.views import GraphQLView
urlpatterns = [
path('graphql/', GraphQLView.as_view(graphiql=True)),
]
Le point d'entrée de l'API GraphQL est défini ici. Un éditeur de requêtes est accessible à l'adresse http://localhost:8000/graphql/.
Création du schéma GraphQL dans app/schema.py
:
import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
from app import models
# Définition des types de données basés sur les modèles Django
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
class AuthorType(DjangoObjectType):
class Meta:
model = models.Profile
class PostType(DjangoObjectType):
class Meta:
model = models.Post
#fields = ("title", "subtitle")
class TagType(DjangoObjectType):
class Meta:
model = models.Tag
# Définition des requêtes de sélection (query)
class Query(graphene.ObjectType):
all_posts = graphene.List(PostType)
posts_by_author = graphene.List(PostType, email=graphene.String())
posts_by_tag = graphene.List(PostType, tag=graphene.String())
post_by_slug = graphene.Field(PostType, slug=graphene.String())
author_by_email = graphene.Field(AuthorType, email=graphene.String())
def resolve_all_posts(self, info):
return models.Post.objects.prefetch_related("tags").select_related("author").all()
def resolve_posts_by_author(self, info, email):
return models.Post.objects.prefetch_related("tags").select_related("author").filter(author__user__email=email)
def resolve_posts_by_tag(self, info, tag):
return models.Post.objects.prefetch_related("tags").select_related("author").filter(tags__name__iexact=tag)
def resolve_post_by_slug(self, info, slug):
return models.Post.objects.prefetch_related("tags").select_related("author").get(slug=slug)
def resolve_author_by_email(self, info, email):
if not info.context.user.is_authenticated():
models.Profile.objects.none()
return models.Profile.objects.select_related("user").get(user__email=email)
# Définition des requêtes de modification (mutation)
class PostMutation(graphene.Mutation):
class Arguments:
subtitle = graphene.String(required=True)
id = graphene.ID()
post = graphene.Field(PostType)
@classmethod
def mutate(cls, root, info, subtitle, id):
post = models.Post.objects.get(pk=id)
post.subtitle = subtitle
post.save()
return PostMutation(post=post)
class Mutation(graphene.ObjectType):
update_post = PostMutation.Field()
# Définition du schéma GraphQL à partir des queries et des mutations
schema = graphene.Schema(query=Query, mutation=Mutation)
Une mutation peut aussi se baser sur un formulaire Django : voir les classes DjangoFormMutation
et DjangoModelFormMutation
dans la documentation.
Exemple de requête GraphQL :
query {
allPosts {
id
title
subtitle
author {
user {
email
}
}
tags {
name
}
}
}
Exemple de mutation GraphQL :
mutation {
updatePost(id: 1, subtitle: "new subtitle") {
post {
id
title
subtitle
}
}
}
Partie client : Vue
Du côté client on peut mettre en place une application Vue.js. Le plugin Vue Apollo permet de gérer les requêtes GraphQL dans un projet Vue.
Les fragments constituent des morceaux de requêtes GraphQL et sont définis dans un fichier fragments.js
:
import gql from 'graphql-tag'
export const authorFragment = {
author: gql`
fragment AuthorFragment on AuthorType {
author {
user {
email
firstName
lastName
}
}
}
`,
}
export const postFragment = {
post: gql`
fragment PostFragment on PostType {
title
subtitle
publishDate
published
metaDescription
slug
tags {
name
}
}
`,
}
Dans les composants Vue, les requêtes ressemblent à ceci :
import gql from 'graphql-tag'
const posts = await this.$apollo.query({
query: gql`
query {
allPosts {
...PostFragment
...AuthorFragment
}
}
${postFragment}
${authorFragment}
`,
})
this.allPosts = posts.data.allPosts
const posts = await this.$apollo.query({
query: gql`
query ($tag: String!) {
postsByTag(tag: $tag) {
...PostFragment
...AuthorFragment
}
}
${postFragment}
${authorFragment}
`,
variables: {
tag: this.$route.params.tag,
},
})
this.posts = posts.data.postsByTag
const post = await this.$apollo.query({
query: gql`
query ($slug: String!) {
postBySlug(slug: $slug) {
...PostFragment
...AuthorFragment
}
}
${postFragment}
${authorFragment}
`,
variables: {
slug: this.$route.params.slug,
},
})
this.post = post.data.postBySlug
const user = await this.$apollo.query({
query: gql`
query ($email: String!) {
authorByEmail(email: $email) {
website
bio
user {
firstName
lastName
email
}
postSet {
...PostFragment
}
}
}
${postFragment}
`,
variables: {
email: this.$route.params.email,
},
})
this.author = user.data.authorByEmail
Dans les composants Vue, les mutations ressemblent à ceci :
import gql from 'graphql-tag'
this.$apollo.mutate({
mutation: gql`
mutation ($id: ID!, $subtitle: String!) {
updatePost(id: $id, subtitle: $subtitle) {
...PostFragment
...AuthorFragment
}
}
${postFragment}
${authorFragment}
`,
variables: {
id: this.post.id,
subtitle: this.post.subtitle,
}
})