Part 1: Django Graphene
In this tutorial we are going to build a Todo app with Gatsby as the frontend and Django Graphene as the backend using test driven development (TDD), and Docker Compose.
In Part 1 of this tutorial our focus will be on building the Django Graphene server. In Part 2 we will build the frontend with Gatsby.
You will need to be comfortable with Django going forward. To bring yourself up to speed on Django check out the official Django tutorial.
Dockerizing Django
What is Graphene and GraphQL? Quoting from the official Graphene-Django docs:
Graphene-Django is built on top of Graphene. Graphene-Django provides some additional abstractions that make it easy to add GraphQL functionality to your Django project.
In short what the above citation is saying, we need Graphene-Django to integrate GraphQL in our Django web app.
What is GraphQL? Quoting from the official GraphQL docs:
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
official GraphQL docs
In a nutshell what they are saying in the above citation is that a Rest Framework will typically have to make multiple requests to multiple endpoints to get all the data you need for your front-end whereas with GraphQL you only need to send one request specifying the data you need. There is a wonderful in depth discussion on the differences between REST and GraphQL here.
Lets start by building out our directory structure.
Project Directory Structure
mkdir todo-app-graphql
cd todo-app-graphql
mkdir server
mkdir db_backups
touch docker-compose.yml
touch Makefile
touch .env
git init
touch .gitignore
touch .dockerignore
touch server/Dockerfile
touch server/requirements.txt
Populate .dockerignore File
There are a number of files we don’t want copied to our docker image so we exclude them with the following:
# Git
.git
.gitignore
# Docker
.docker
# Python
app/__pycache__/
app/*/__pycache__/
app/*/*/__pycache__/
app/*/*/*/__pycache__/
.env/
.venv/
venv/
# Local PostgreSQL data
data/
Populate .gitignore File
Pulling from the Github .gitignore boilerplate, gitignore/Python.gitignore we populate the .gitignore file:
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
Our Project Directories and Files
-
todo-app-graphql
- db_backups
-
server
- Dockerfile
- requirements.txt
- .env
- docker-compose-dev.yml
- docker-compose.yml Makefile
Populate the Django Dockerfile
cd server
sudo nano Dockerfile
Add the following code to todo-app-graphql/server/Dockerfile:
# /todo-app-graphql/server/Dockerfile
FROM python:3
# Set PYTHONUNBUFFERED to 1 so that Python outputs to the container logs.
ENV PYTHONBUFFERED 1
RUN mkdir /server
WORKDIR /server
COPY requirements.txt /server/
RUN pip3 install -r requirements.txt
COPY . /server/
CMD python manage.py collectstatic --no-input;python manage.py makemigrations;python manage.py migrate
Populate the .env File @ Project Root
sudo nano .env
Add the following code to todo-app-graphql/.env:
DATABASE_NAME=db_todo
DATABASE_USER=user_todo
DATABASE_PASSWORD=mypassword
DATABASE_HOST=database
DATABASE_PORT=5432
HOST=localhost
SERVER_PORT=5555
DJANGO_SETTINGS_MODULE=todo_proj.settings
The Docker-Compose File
# dockerized-django/docker-compose.yml
version: '3'
services:
database:
container_name: postgres
image: postgres:latest
env_file:
- .env
environment:
- POSTGRES_DB=$DATABASE_NAME
- POSTGRES_USER=$DATABASE_USER
- POSTGRES_PASSWORD=$DATABASE_PASSWORD
- POSTGRES_PORT=$DATABASE_PORT
volumes:
- ./postgres-data:/var/lib/postgresql/data
server:
build: ./server/
container_name: server
ports:
- '$SERVER_PORT:$SERVER_PORT'
volumes:
- ./server/:/server
command: python manage.py runserver 0.0.0.0:$SERVER_PORT
environment:
- DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
- PGHOST=$DATABASE_HOST
env_file:
- .env
depends_on:
- database
volumes:
postgres-data:
Populate the Django requirements.txt File
Django>=3.0
psycopg2>=2.8.6
graphene-django>=2.2.0
django-autoslug>=1.9.6
django-filter>=2.0.0
django-graphql-jwt>=0.1.5
django-mptt>=0.11.0
Pillow>=6.1.0
django-cors-headers>=3.1.0
django-jwt-auth>=0.0.2
PyJWT>=1.7.1
coverage>=5.1
freezegun>=0.3.15
python-dateutil>=2.8.1
pytest-django>=3.9.0
Create a New Django Project
# create django project
# important!! note the trailing .
docker-compose run server django-admin startproject todo_proj .
Edit the New Django Project Setting File
The default database for a new Django project is sql lite but we want postgresql so we need to edit the DATABASES section of our projects settings.py file.
todo_proj/settings.py
import os
DATABASES = {
'default':
{
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DATABASE_NAME'),
'USER': os.environ.get('DATABASE_USER'),
'PASSWORD': os.environ.get('DATABASE_PASSWORD'),
'HOST': os.environ.get('DATABASE_HOST'),
'PORT': os.environ.get('DATABASE_PORT'),
}
}
Migrate and Create the Django Todo App
# migrate
docker-compose run server python manage.py migrate
docker-compose up -d database
docker-compose up -d server
docker-compose run server django-admin startapp todo_app
docker-compose run server python manage.py createsuperuser
Add Todo App to Django Settings
Now we add our new Todo app to the Django settings.py file:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todo_app',
]
Test the App
To see if Django is up and running go to http://localhost:5555/admin and login with your superuser credentials and if all went well you should see our projects admin page.
Note if you get an error try running migrate again:
docker-compose run server ./manage.py migrate

Coming Up in Part 2: TDD of Django Back End
In part 2 we begin building out the Django Graphene server for our Todo app using TDD, (test driven development).