besil
Posted on December 10, 2021
My Django-Svelte workflow for fullstack development
- Motivation
- About this post
- The app
- Adding Authentication
- Rest API
- Building for Production
- Conclusions
Motivation
I recently discovered Svelte and became my first choice for personal projects and MVP/POCs.
As a backend developer, I struggled a lot with frameworks like React or Vue, looking for a productive workflow. Svelte is dead simple compared to them, in my opinion. And yet very powerful.
However, coming from Django, I still struggled on stuff like security or form validation: if I want a SPA, do I really need to use only Django Rest Framework and implement everything through REST API?
Take authentication for example: Django has built-in views for doing it in a secure way. Do I really need to re-implement it every time client-side using JWT? Looks like a waste of time, for my productivity at least.
I really like Django approach “batteries included”, since it ships with best practices and it makes me a lot more focused on the goal I want to achieve.
On the other hand, I still want the flexibility and reactivity provided by a SPA framework.
So, after multiple projects, I'd like to share in this blog post my personal take aways and show how I scaffold my projects, in order to get:
- a Django website, using the powerful template system for authentication, registration and password recovery (using Django built in views)
- a Svelte app served by Django, with hot reloading during development
- some Rest API to provide data to the SPA app using the awesome Django REST Framework
This way helps me a lot, because it keeps the best of the three worlds without compromising productivity.
About this post
Please consider the following as my personal vademecum for setting up a Django+Svelte project quickly with basic usefules dependencies (such as whitenoise, black...) and authentication (which I haven't found much covered online).
It is a concentrated boilerplate of my favourites tweaks on Django that made me proficient. Feel free to ignore those who don't interests you. Any feedback is very welcome.
I’m using Svelte because is my favourite, but the approach should work for Vue and React, with obvious adjustments.
A medium Django/Python and Svelte knowledge is required.
The app
Prerequisites
My tools for the job are:
Setup
My setup is very similar to the one in the DRF quickstart, but I prefer poetry to pip
mkdir django-svelte-sample-app
cd django-svelte-sample-app
Django setup
For poetry init, go for the defaults and add the basic dependencies for our Django project after
poetry init
poetry add django whitenoise
poetry add --dev black
Set Django up (note the final dot)
poetry run django-admin startproject myapp .
Svelte setup
npx degit sveltejs/template frontend
cd frontend
npm install
cd ..
Setup
You should have the following structure:
$ find .
.
./frontend
./frontend/README.md
./frontend/rollup.config.js
./frontend/public
./frontend/public/index.html
./frontend/public/global.css
./frontend/public/favicon.png
./frontend/.gitignore
./frontend/package-lock.json
./frontend/package.json
./frontend/scripts
./frontend/scripts/setupTypeScript.js
./frontend/src
./frontend/src/App.svelte
./frontend/src/main.js
./pyproject.toml
./myapp
./myapp/asgi.py
./myapp/__init__.py
./myapp/settings.py
./myapp/urls.py
./myapp/wsgi.py
./manage.py
./poetry.lock
./.venv/....
./frontend/node_modules/...
Code: Django - settings.py
We will configure some settings for Django in order to:
- enable Django Debug using Environment Variables
- configure logging
- read templates from a root folder (instead of subapps templates)
- managing static files with Whitenoise
We will modify the myapp/settings.py file
Introducing DJANGO_DEBUG
This is very useful when working with Docker or Heroku and you want to dinamically turn on/off the Django debug mode.
Replace
DEBUG=True
with
import os
DEBUG = "DJANGO_DEBUG" in os.environ and os.environ["DJANGO_DEBUG"] == "ON"
Configure logging
I usually configure logging as the following:
logging_level = (
"INFO" if "LOGGING_LEVEL" not in os.environ else os.environ["LOGGING_LEVEL"]
)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"console": {
"format": "[%(asctime)s][%(levelname)8s][%(name)16.16s]@[%(lineno)5s]$ %(message)s"
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "console",
},
},
"root": {
"handlers": ["console"],
"level": "WARNING",
"propagate": False,
},
"loggers": {
"django.server": {
"level": "WARNING",
"handlers": ["console"],
"propagate": False,
},
"myapp": {
"level": logging_level,
"handlers": ["console"],
"propagate": False,
},
},
}
You can now easily change the log level using the LOGGING_LEVEL env variable. Feel free to change the format as you prefer!
Django Template folder
I generally prefer to have all my .html templates inside a root level templates folder instead of every app having appname/templates/appname folder.
Modify the TEMPLATES variable from:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
to
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["myapp/templates"], # <-- here
...
},
]
and create the folder:
mkdir -p myapp/templates
Django static files
As mentioned, I really love Whitenoise for serving static files directly from Django. This helps me a lot during development and deploying because I can stay within Django.
We now create a staticfiles folder inside myapp, where files will be collected when we go to production, and the folder that will contain the compiled Svelte files, that will be served as Django static files (we will see it later)
mkdir -p myapp/static/frontend
We need to configure out static configurations accordingly:
STATIC_ROOT = os.path.join(BASE_DIR, "myapp", "staticfiles")
STATIC_URL = "/static/"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
STATICFILES_DIRS = (os.path.join(BASE_DIR, "myapp", "static"),)
And do not forget to add Whitenoise to the list of middlewares:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # <-- here
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
...
]
Finish Django configuration
Let's complete Django configuration creating applying the migrations and starting up the server, just to be sure no warnings came up
If you want to just check that everything is ok, you can run:
DJANGO_DEBUG=ON poetry run python manage.py makemigrations
DJANGO_DEBUG=ON poetry run python manage.py migrate
DJANGO_DEBUG=ON poetry run python manage.py runserver
You should see
Watching for file changes with StatReloader
[2021-12-09 18:31:03,519][ INFO][django.utils.aut]@[ 643]$ Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
December 09, 2021 - 18:31:03
Django version 4.0, using settings 'myapp.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
So far, so good. We just instructed Django to do some useful stuff through standard configuration. Let's add the Svelte part in order to see something.
Code: Svelte
For now, we will just expose the Svelte default app from Django.
The idea here is: we will serve the files from Django static/frontend dir and we will develop from Django (ie. localhost:8000) instead of localhost:5000 (default rollup dev server).
All we have to do is configure rollup to emit the build files in the static django folder.
Go to frontend/rollup.config.js and change the export default like this:
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: '../myapp/static/frontend/bundle.js' // <-- here
},
plugins: [
...
!production && livereload('../myapp/static/frontend'), // <-- here
...
],
...
}
Move the frontend/public/ files to django
mv frontend/public/favicon.png myapp/static/frontend/
mv frontend/public/global.css myapp/static/frontend/
Now in a terminal starts rollup dev server
cd frontend
npm run dev
And you will see the bundle files created under the Django folder
$ ls myapp/static/frontend
bundle.css bundle.js bundle.js.map favicon.png global.css index.html
We will leave the rollup dev server up so that we will have the hot reload while working on the Django side, so leave the server up and change terminal.
In order to serve it from Django, we need to render the template using a standard view
Code: Django - the SPA view
We now create a dedicated Django app that will just render the Svelte app
cd myapp
poetry run django-admin startapp spa
cd ..
mkdir -p myapp/templates/spa
mv frontend/public/index.html myapp/templates/spa/
Replace the content of myapp/spa/views.py with the following:
from django.views.generic import TemplateView
class SpaView(TemplateView):
template_name = "spa/index.html"
Modify the myapp/templates/spa/index.html using static resources from the frontend
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href="{% static 'frontend/favicon.png' %}"> <!-- <-- here -->
<link rel='stylesheet' href="{% static 'frontend/global.css' %}"> <!-- <-- here -->
<link rel='stylesheet' href="{% static 'frontend/bundle.css' %}"> <!-- <-- here -->
<script defer src="{% static 'frontend/bundle.js' %}"></script> <!-- <-- here -->
</head>
<body>
</body>
</html>
Add the view in the myapps/urls.py
# myapps/urls.py
from django.contrib import admin
from django.urls import path
from myapp.spa.views import SpaView # <-- here
urlpatterns = [
path("admin/", admin.site.urls),
path("", SpaView.as_view(), name="spa"), # <-- here
]
and finally, in order to add it in the installed apps, we need to:
- modify myapp/spa/apps.py
class SpaConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "myapp.spa" # <-- here
- add myapp.spa in the INSTALLED_APPS in myapp/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
...
"django.contrib.staticfiles",
"myapp.spa", # <-- here
]
Test it
From the root folder of the project (ie the one with the frontend and myapp folders inside), run in two different terminal windows:
# 1st shell - svelte
cd frontend
npm run dev
# 2nd shell - django
DJANGO_DEBUG=ON poetry run python manage.py runserver
Go to localhost:8000 and you will see the Svelte app served directly from Django.
Try changing frontend/src/App.svelte or frontend/src/main.js, hit Ctrl+R and you will see it hot reloaded (from Django. Remember to disable cache or force the browser to refresh the page if you don't see any change).
Don't forget that you need the "npm run dev" in background in order to keep sync the bundle.* files under Django static folder.
Secure our SPA the Django way
Now we can leverage the standard Django features, such as authentication.
First, secure our View extending the LoginRequiredMixin. In myapp/spa/views.py
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
class SpaView(LoginRequiredMixin, TemplateView):
template_name = "spa/index.html"
To enable auth, add in your myapp/settings.py
from django.urls import reverse_lazy
LOGIN_URL = reverse_lazy("login")
LOGIN_REDIRECT_URL = reverse_lazy("spa")
LOGOUT_REDIRECT_URL = reverse_lazy("spa")
and in myapps/urls.py
from django.contrib import admin
from django.urls import path, include # <-- here
from myapp.spa.views import SpaView
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")), # <-- here
path("", SpaView.as_view(), name="spa"),
]
Create the login form template:
mkdir -p myapp/templates/registration
touch myapp/templates/registration/login.html
Add the following to myapp/templates/registration/login.html (taken from the django documentation)
<html>
<head></head>
<body>
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="login">
<input type="hidden" name="next" value="{{ next }}">
</form>
{# Assumes you set up the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
</body>
</html>
Create a super user running in a terminal
poetry run python manage.py createsuperuser
Now if you try to hit localhost:8000, you will be redirected to http://localhost:8000/accounts/login/?next=/.
Try to login with the provided credentials and you will see the Svelte app again.
Hit localhost:8000/accounts/logout to logout.
Rest API
Now we will add a very simple REST API using Django Rest Framework and consume it from the Svelte App.
Note that our Svelte SPA app will leverage DRF SessionAuthentication, since it will invoke the API in the same context of the Django app. This is the most secure way, in my opinion
First, install DRF
poetry add djangorestframework
In addition, since Django 4.0 has just been released, we need to install pytz, since DRF still uses it
poetry add pytz
and add it in myapp/settings.py INSTALLED_APPS
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
'rest_framework', # <-- here
"myapp.spa",
]
Create a new Django app for the API
cd myapp
poetry run django-admin startapp api
cd ..
Add the mock api in myapp/api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from rest_framework.renderers import JSONRenderer
class GreetingApi(APIView):
authentication_classes = [authentication.SessionAuthentication]
permission_classes = [permissions.IsAuthenticated]
renderer_classes = [JSONRenderer]
def get(self, request, format=None):
return Response({"message": "Hello world"})
Note that:
- we are using the default Django Session Authentication (via login form)
- the API is available only for authenticated users
Now adjust frontend/src/App.svelte
<script>
import { onMount } from "svelte";
export let name;
let apimessage = "Waiting for server...";
onMount(async () => {
let resp = await fetch("/api/greet").then((res) => res.json());
console.log(resp);
apimessage = JSON.stringify(resp);
});
</script>
<main>
<h1>Hello {name}!</h1>
<p>
Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn
how to build Svelte apps.
</p>
<h3>Data from server</h3>
{apimessage}
</main>
Add the API to myapp/urls.py
from django.contrib import admin
from django.urls import path, include
from myapp.spa.views import SpaView
from myapp.api.views import GreetingApi # <-- here
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("api/greet", GreetingApi.as_view()), # <-- here
path("", SpaView.as_view(), name="spa"),
]
Hit localhost:8000, login and enjoy your Django based SPA using Server Side Authentication and Rest API
Building for Production
In order to make our app ready to Production, we need:
- Gunicorn installed
poetry add gunicorn
- collect all static files
poetry run python manage.py collectstatic
- configure the allowed hosts
#myapp/settings.py
# change ALLOWED_HOSTS = [] to
ALLOWED_HOSTS = ["localhost"]
Now you are ready to run
poetry run gunicorn myapp.wsgi
Go to localhost:8000 and enjoy your gunicorn served app
Conclusions
In this quite long walkthrough, we saw different things:
- set up a Django app with my personal useful tweaks
- set up an integrated Svelte app served by Django
- configure Django built-in authentication to secure our SPA
- providing some Rest API for the SPA to consume
- packing everything up for Production
You can find all the code here on github.
I hope this post can help you make your life as developer easier. You can find Docker integration and other utility scripts for local development here.
Feel free to comment, I'd be glad to receive any feedback your have.
Posted on December 10, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.