在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):mbrochh/django-graphql-apollo-react-demo开源软件地址(OpenSource Url):https://github.com/mbrochh/django-graphql-apollo-react-demo开源编程语言(OpenSource Language):Python 49.9%开源软件介绍(OpenSource Introduction):A Django + GraphQL + Apollo + React Stack DemoThis repo contains the code shown at the Singapore Djangonauts June 2017 Meetup A video of the workshop can be found on engineers.sg: The sound in the video is messed up. I'm looking for a venue and an audience to record the talk one more time :( This README was basically the "slides" for the workshop, so if you want to learn as well, just keep on reading! In this workshop, we will address the following topics: Part 1: The Backend
Part 2: The Frontend
Part 3: Advanced TopicsI am planning to keep this repo alive and add some more best practices as I figure them out at work. Some ideas:
If you have more ideas, please add them in the issue tracker! Before you start, you should read a little bit about GraphQL and Apollo and python-graphene. If you have basic understanding of Python, Django, JavaScript and ReactJS, you should be able to follow this tutorial and copy and paste the code snippets shown below and hopefully it will all work out nicely. The tutorial should give you a feeling for the necessary steps involved when building a web application with Django, GraphQL and ReactJS. If you find typos or encounter other issues, please report them at the issue tracker. Part 1: The BackendCreate a new Django ProjectFor this demonstration we will need a backend that can serve our GraphQL API. We will chose Django for this, so the first thing we want to do is to create a new Django project. If you are new to Python, you need to read about virtualenvwrapper, first. mkdir -p ~/Projects/django-graphql-apollo-react-demo/src
cd ~/Projects/django-graphql-apollo-react-demo/src
mkvirtualenv django-graphql-apollo-react-demo
pip install django
pip install pytest
pip install pytest-django
pip install pytest-cov
pip install mixer
django-admin startproject backend
cd backend
./manage.py migrate
./manage.py createsuperuser
./manage.py runserver
We like to build our Django apps in a test-driven manner, so let's also create a few files to setup our testing framework: # File: ./backend/backend/test_settings.py
from .settings import * # NOQA
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.MD5PasswordHasher',
)
DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'
Create a Simple Django AppAt this point, our Django project is pretty useless, so let's create a simple Twitter-like app that allows users to create messages. It's a nice example for an app that has a CreateView, a ListView and a DetailView. cd ~/Projects/django-graphql-apollo-react-demo/src/backend
django-admin startapp simple_app
cd simple_app
mkdir tests
touch tests/__init__.py
touch tests/test_models.py Whenever we create a new app, we need to tell Django that this app is now part of our project: # File: ./backend/backend/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'simple_app',
] First, let's create a test for our upcoming new model. The model doesn't do much, so we will simply test if we are able to create an instance and save it to the DB. We are using mixer to help us with the creation of test-fixtures. # File: ./backend/simple_app/tests/test_models.py
import pytest
from mixer.backend.django import mixer
# We need to do this so that writing to the DB is possible in our tests.
pytestmark = pytest.mark.django_db
def test_message():
obj = mixer.blend('simple_app.Message')
assert obj.pk > 0 Next, let's create our # File: ./backend/simple_app/models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
class Message(models.Model):
user = models.ForeignKey('auth.User')
message = models.TextField()
creation_date = models.DateTimeField(auto_now_add=True) Let's also register the new model with the Django admin, so that we can add entries to the new table: # File: ./backend/simple_app/admin.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from . import models
admin.site.register(models.Message) Whenever we make changes to a model, we need to create and run a migration: cd ~/Projects/django-graphql-apollo-react-demo/src/backend
./manage.py makemigrations simple_app
./manage.py migrate
Add GraphQL to DjangoSince we have now a Django project with a model, we can start thinking about adding an API. We will use GraphQL for that. cd ~/Projects/django-graphql-apollo-react-demo/src/backend
pip install graphene-django Whenever we add a new app to Django, we need to update our # File: ./backend/backend/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'graphene_django',
'simple_app',
]
GRAPHENE = {
'SCHEMA': 'backend.schema.schema',
} Now we need to create our main # File: ./backend/backend/schema.py
import graphene
class Queries(
graphene.ObjectType
):
dummy = graphene.String()
schema = graphene.Schema(query=Queries) Finally, we need to hook up GraphiQL in our Django # File: ./backend/backend/urls.py
from django.conf.urls import url
from django.contrib import admin
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^graphiql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
url(r'^gql', csrf_exempt(GraphQLView.as_view(batch=True))),
]
Add Message-DjangoObjectType to GraphQL SchemaIf you have used Django Rest Framework before, you know that you have to create serializers for all your models. With GraphQL it is very similar: You have to create Types for all your models. We will begin with creating a type for our Message model and when we are at it, we will also create a query that returns all messages. In good TDD fashion, we begin with a test for the type and a test for the query: # File: ./backend/simple_app/tests/test_schema.py
import pytest
from mixer.backend.django import mixer
from . import schema
pytestmark = pytest.mark.django_db
def test_message_type():
instance = schema.MessageType()
assert instance
def test_resolve_all_messages():
mixer.blend('simple_app.Message')
mixer.blend('simple_app.Message')
q = schema.Query()
res = q.resolve_all_messages(None, None, None)
assert res.count() == 2, 'Should return all messages' In order to make our test pass, we will now add our type and the query: # File: ./backend/simple_app/schema.py
import graphene
from graphene_django.types import DjangoObjectType
from . import models
class MessageType(DjangoObjectType):
class Meta:
model = models.Message
interfaces = (graphene.Node, )
class Query(graphene.AbstractType):
all_messages = graphene.List(MessageType)
def resolve_all_messages(self, args, context, info):
return models.Message.objects.all() Finally, we need to update your main # File: ./backend/backend/schema.py
import graphene
import simple_app.schema
class Queries(
simple_app.schema.Query,
graphene.ObjectType
):
dummy = graphene.String()
schema = graphene.Schema(query=Queries)
{
allMessages {
id, message
}
} The query # File: ./backend/simple_app/tests/test_schema.py
from graphql_relay.node.node import to_global_id
def test_resolve_message():
msg = mixer.blend('simple_app.Message')
q = schema.Query()
id = to_global_id('MessageType', msg.pk)
res = q.resolve_messages({'id': id}, None, None)
assert res == msg, 'Should return the requested message' To make the test pass, let's update our schema file: # File: ./backend/simple_app/schema.py
from graphql_relay.node.node import from_global_id
class Query(graphene.AbstractType):
message = graphene.Field(MessageType, id=graphene.ID())
def resolve_message(self, args, context, info):
rid = from_global_id(args.get('id'))
# rid is a tuple: ('MessageType', '1')
return models.Message.objects.get(pk=rid[1])
[...]
Add Mutation to GraphQL SchemaOur API is able to return items from our DB. Now it is time to allow to write messages. Anything that changes data in GraphQL is called a "Mutation". We want to ensure that our mutation does three things:
# File: ./backend/simple_app/tests/test_schema.py
from django.contrib.auth.models import AnonymousUser
from django.test import RequestFactory
def test_create_message_mutation():
user = mixer.blend('auth.User')
mut = schema.CreateMessageMutation()
data = {'message': 'Test'}
req = RequestFactory().get('/')
req.user = AnonymousUser()
res = mut.mutate(None, data, req, None)
assert res.status == 403, 'Should return 403 if user is not logged in'
req.user = user
res = mut.mutate(None, {}, req, None)
assert res.status == 400, 'Should return 400 if there are form errors'
assert 'message' in res.formErrors, (
'Should have form error for message field')
res = mut.mutate(None, data, req, None)
assert res.status == 200, 'Should return 200 if mutation is successful'
assert res.message.pk == 1, 'Should create new message' With these tests in place, we can implement the actual mutation: # File: ./backend/simple_app/schema.py
import json
class CreateMessageMutation(graphene.Mutation):
class Input:
message = graphene.String()
status = graphene.Int()
formErrors = graphene.String()
message = graphene.Field(MessageType)
@staticmethod
def mutate(root, args, context, info):
if not context.user.is_authenticated():
return CreateMessageMutation(status=403)
message = args.get('message', '').strip()
# Here we would usually use Django forms to validate the input
if not message:
return CreateMessageMutation(
status=400,
formErrors=json.dumps(
{'message': ['Please enter a message.']}))
obj = models.Message.objects.create(
user=context.user, message=message
)
return CreateMessageMutation(status=200, message=obj)
class Mutation(graphene.AbstractType):
create_message = CreateMessageMutation.Field() This new # File: ./backend/backend/schema.py
class Mutations(
simple_app.schema.Mutation,
graphene.ObjectType,
):
pass
[...]
schema = graphene.Schema(query=Queries, mutation=Mutations)
mutation {
createMessage(message: "Test") {
status,
formErrors,
message {
id
}
}
} Add JWT-Authentication to DjangoOne of the most common things that every web application needs is authentication. During my research I found django-graph-auth, which is based on Django Rest Frameworks JWT plugin. There is als pyjwt, which would allow you to implement your own endpoints. I didn't have the time to evaluate We will also need to install django-cors-headers because during local development, the backend and the frontend are served from different ports, so we need to enable to accept requests from all origins. cd ~/Projects/django-graphql-apollo-react-demo/src/backend
pip install djangorestframework
pip install djangorestframework-jwt
pip install django-cors-headers Now we need to update our Django settings with new settings related to the
# File: ./backend/backend/settings.py**
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'graphene_django',
'simple_app',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
CORS_ORIGIN_ALLOW_ALL = True
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
] Now that Rest Framework is configured, we need to add a few URLs: # File: ./backend/backend/urls.py
[...]
from rest_framework_jwt.views import obtain_jwt_token
from rest_framework_jwt.views import refresh_jwt_token
from rest_framework_jwt.views import verify_jwt_token
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^graphiql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
url(r'^gql', csrf_exempt(GraphQLView.as_view(batch=True))),
url(r'^api-token-auth/', obtain_jwt_token),
url(r'^api-token-refresh/', refresh_jwt_token),
url(r'^api-token-verify/', verify_jwt_token),
]
Part 2: The FrontendIn Part 1, we create a Django backend that serves a GraphQL API. In this part we will create a ReactJS frontend that consumes that API. Create a new React ProjectFacebook has released a wonderful command line tool that kickstarts a new ReactJS project with a powerful webpack configuration. Let's use that: cd ~/Projects/django-graphql-apollo-react-demo/src
npm install -g create-react-app
create-react-app frontend
cd frontend
yarn start
Add ReactRouter to Reactcd ~/Projects/django-graphql-apollo-react-demo/src/frontend
yarn add react-router-dom First, we need to replace the example code in 全部评论
专题导读
上一篇:octokit/graphql-schema: GitHub’s GraphQL Schema with validation. Automatically ...发布时间:2022-06-13下一篇:gql-dart/gql: Libraries supporting GraphQL in Dart发布时间:2022-06-13热门推荐
热门话题
阅读排行榜
|
请发表评论