Deploying a separate backend [Django 4.x, PostgreSQL 12.9, Gunicorn, Nginx и django-cors-headers] and frontend [Node.js LTS (16.x), @vue/cli (Vue CLI), Vue v3, Vue Router v4 и Vue Apollo v4] using Strawberry as GraphQL API
I tested Build a Blog Using Django, Vue, and GraphQL tutorial by Dane Hillard on Ubuntu 20.04 (LTS) x64 virtual server in DigitalOcean. But since the tutorial uses Django 3.x, Vue 2 and Graphene-Django (which is not sufficiently supported now), I rewrote the code using the latest versions available at the end of January 2022 and has replaced Graphene-Django with Strawberry:
- Django==4.0.2
- strawberry-graphql-django 0.2.5
- Node.js LTS (16.x)
- @vue/cli-service@4.5.15
- vue@3.2.29
- vue-router@4.0.12
- @apollo/client@3.5.8
- @vue/apollo-composable@4.0.0-alpha.16
- @vue/apollo-option@4.0.0-alpha.16
Since the backend and frontend in this example work completely independently, we can test their operations separately. This will also allow you to better understand how each component works. You have two options:
Option 1 - leave backend with Graphene-Django unchanged, as in the original exercise, and change only frontend
Option 2 - completely change both backend and frontend, i.e. we will use Django 4.x, Poetry, Strawberry, Vue v3 and Vue Apollo v4.
Next, I provide a list of necessary actions for the initial setup of a remote virtual server and the creation of a new frontend part:
- Make initial server setup
- Make set up of Django with Postgres, Nginx, and Gunicorn on Ubuntu 20.04. Your should install Django==3.1.7 for this Option.
- Follow steps 1 - 4 from original tutorial
- sudo ufw allow 8080 (this opens port 8080 which will be used by frontend)
- install latest stable version of nude.js:
a. curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
b. sudo apt-get install -y nodejs - sudo npm install -g @vue/cli
- vue create frontend (choose - Default (Vue 3) ([Vue 3] babel, eslint)
- cd frontend
- Install Vue Router and Apollo:
~/backend$ vue add router
~/backend$ npm install --save graphql graphql-tag @apollo/client
~/backend$ npm install --save @vue/apollo-composable
~/backend$ npm install --save @vue/apollo-option
- npm run serve (check that frontend works well at http:// IP_address_of_your_server:8080/
- copy and study App.vue, main.js and router.js from this repository at GraphQL_Tutorial/frontend/src/
- vue files in components folder (Vue Components as per Steps 7 & 8) were not changed but also saved here
Thats all.
- I'm assuming you just installed Ubuntu 20.04 so make initial server setup
- Install Python, PostgreSQL, Nginx, cURL, tree:
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl tree
- Make Postgres database setup (you need to define myprojectuser, myproject and 'password' and copy them in settings.py later):
sudo -u postgres psql CREATE DATABASE myproject; CREATE USER myprojectuser WITH PASSWORD 'password'; ALTER ROLE myprojectuser SET client_encoding TO 'utf8'; ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed'; ALTER ROLE myprojectuser SET timezone TO 'UTC'; GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser; \q
- Install Poetry
sudo apt install python3.8-venv
sudo curl -sSL https://install.python-poetry.org | python3 -
export PATH="/home/your_user_name/.local/bin:$PATH"
echo $PATH
poetry --version
- Create project backend with virtual environment and install Django in it:
mkdir backend
cd backend
~/backend$ poetry init --no-interaction --dependency Django==4.0.2
~/backend$ poetry install
~/backend$ poetry shell
(backend-...-py3.8) ~/backend$ django-admin startproject backend ~/backend
(backend-...-py3.8) ~/backend$ tree -L 2
(backend-...-py3.8) ~/backend$ poetry add psycopg2-binary
- Make changes in settings.py for ALLOWED_HOSTS, DATABASES and STATIC_ROOT. Use Pathlib in Your Django Settings File:
# nano backend/settings.py from pathlib import Path BASE_DIR = Path(__file__).resolve(strict=True).parent.parent TEMPLATES_DIR = BASE_DIR.joinpath('templates') ALLOWED_HOSTS = ['your_server_domain_or_IP', 'localhost'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'myproject', 'USER': 'myprojectuser', 'PASSWORD': 'password', 'HOST': 'localhost', 'PORT': '', } } STATIC_URL = 'static/' STATIC_ROOT = BASE_DIR / 'static'
- Run Django Migrations, create Superuser and see the default Django index page:
(backend-...-py3.8) ~/backend$ python manage.py makemigrations (backend-...-py3.8) ~/backend$ python manage.py migrate (backend-...-py3.8) ~/backend$ python manage.py createsuperuser (backend-...-py3.8) ~/backend$ python manage.py collectstatic sudo ufw allow 8000 (backend-...-py3.8) ~/backend$ python manage.py runserver 0.0.0.0:8000
- Creating systemd Socket and Service Files for Gunicorn
(backend-...-py3.8) ~/backend$ poetry add gunicorn (backend-...-py3.8) ~/backend$ poetry show --tree sudo nano /etc/systemd/system/gunicorn.socket [Unit] Description=gunicorn socket [Socket] ListenStream=/run/gunicorn.sock [Install] WantedBy=sockets.target
(backend-...-py3.8) ~/backend$ poetry env info sudo nano /etc/systemd/system/gunicorn.service [Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] User=your_user_name Group=www-data WorkingDirectory=/home/your_user_name/backend ExecStart=/home/your_user_name/.cache/pypoetry/virtualenvs/backend-...-py3.8/bin/gunicorn \ --access-logfile - \ --workers 3 \ --bind unix:/run/gunicorn.sock \ backend.wsgi:application [Install] WantedBy=multi-user.target
sudo ln -s /etc/nginx/sites-available/backend /etc/nginx/sites-enabled sudo systemctl start gunicorn.socket sudo systemctl enable gunicorn.socket sudo systemctl daemon-reload sudo systemctl restart gunicorn
- Configure Nginx to Proxy Pass to Gunicorn
sudo nano /etc/nginx/sites-available/backend server { listen 80; listen [::]:80; server_name your_server_domain_or_IP; location = /favicon.ico { access_log off; log_not_found off; } location /static/ { root /home/your_user_name/backend; } location / { include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; } }
sudo ln -s /etc/nginx/sites-available/backend /etc/nginx/sites-enabled sudo nginx -t sudo systemctl restart nginx sudo ufw delete allow 8000 sudo ufw allow 'Nginx Full' sudo ufw status verbose
At this stage you should be able to access your site and see Django welcome page.
- Create the Django Blog Application (use command to access virtual environment)
(backend-...-py3.8) ~/backend$ python manage.py startapp blog (backend-...-py3.8) ~/backend$ nano backend/settings.py INSTALLED_APPS = [ ... 'blog', ]
- Study and copy blog/models.py and blog/admin.py from here.
- Study and copy blog/schema.py from here. Then install Strawberry and django-cors-headers:
(backend-...-py3.8) ~/backend$ poetry add strawberry-graphql-django (backend-...-py3.8) ~/backend$ poetry add django-cors-headers nano backend/settings.py INSTALLED_APPS = [ ... 'strawberry.django', 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ... ] CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = [ 'http://localhost:8080', 'http://your_server_domain_or_IP:8080', ]
nano backend/urls.py from django.contrib import admin from django.urls import include, path from strawberry.django.views import AsyncGraphQLView from django.views.decorators.csrf import csrf_exempt from blog.schema import schema urlpatterns = [ path('admin/', admin.site.urls), path('graphql', csrf_exempt(AsyncGraphQLView.as_view(schema=schema))), ]
- Refresh Django and Gunicorn:
(backend-...-py3.8) ~/backend$ python manage.py makemigrations
(backend-...-py3.8) ~/backend$ python manage.py migrate
(backend-...-py3.8) ~/backend$ python manage.py collectstatic
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
-
Visit to http://your_server_domain_or_IP/admin and add (input) at least three posts
-
Visit to http://your_server_domain_or_IP/graphql and review your Query. This is your GraphQL API endpoint.
-
Thats all for backend. Then do actions 4-12 from Option 1 and you should see your blog up and running
-
Your backend folder structure should be as follows:
~/backend$ tree -L 2
├── backend
│ ├── __init__.py
│ ├── __pycache__
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── blog
│ ├── __init__.py
│ ├── __pycache__
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── schema.py
│ ├── tests.py
│ └── views.py
├── manage.py
├── poetry.lock
├── pyproject.toml
└── static
├── admin
└── graphiql.html
- Graphene-Django has DjangoObjectType which includes all the fields in the model by default. In this Strawberry schema.py we have defined each field separately
- To restrict users from accessing the GraphQL API page the standard Django LoginRequiredMixin can be used. Unfortunately LoginRequiredMixin doesn't support async yet.
# views.py from django.contrib.auth.mixins import LoginRequiredMixin from strawberry.django.views import GraphQLView class PrivateGraphQLView(LoginRequiredMixin, GraphQLView): login_url = '/login/'
# urls.py from your_app.views import PrivateGraphQLView urlpatterns = [ # other paths, path('graphql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, schema=schema))),
- Deployment to productiond: disable GraphiQL and Introspection
- When is it safe to use the csrf_exempt decorator?