My Django-Svelte setup for fullstack development

besil

besil

Posted on December 10, 2021

My Django-Svelte setup for fullstack development

My Django-Svelte workflow for fullstack development

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Set Django up (note the final dot)

poetry run django-admin startproject myapp .
Enter fullscreen mode Exit fullscreen mode

Svelte setup

npx degit sveltejs/template frontend
cd frontend
npm install
cd ..
Enter fullscreen mode Exit fullscreen mode

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/...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

with

import os
DEBUG = "DJANGO_DEBUG" in os.environ and os.environ["DJANGO_DEBUG"] == "ON"
Enter fullscreen mode Exit fullscreen mode

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,
        },
    },
}
Enter fullscreen mode Exit fullscreen mode

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",
            ],
        },
    },
]
Enter fullscreen mode Exit fullscreen mode

to

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": ["myapp/templates"], # <-- here
        ...
    },
]
Enter fullscreen mode Exit fullscreen mode

and create the folder:

mkdir -p myapp/templates
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"),)
Enter fullscreen mode Exit fullscreen mode

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",
    ...
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
        ...
    ],
    ...
}
Enter fullscreen mode Exit fullscreen mode

Move the frontend/public/ files to django

mv frontend/public/favicon.png myapp/static/frontend/
mv frontend/public/global.css myapp/static/frontend/
Enter fullscreen mode Exit fullscreen mode

Now in a terminal starts rollup dev server

cd frontend
npm run dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 2nd shell - django
DJANGO_DEBUG=ON poetry run python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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"),
]
Enter fullscreen mode Exit fullscreen mode

Create the login form template:

mkdir -p myapp/templates/registration
touch myapp/templates/registration/login.html
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Create a super user running in a terminal

poetry run python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

In addition, since Django 4.0 has just been released, we need to install pytz, since DRF still uses it

poetry add pytz
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

Create a new Django app for the API

cd myapp
poetry run django-admin startapp api
cd ..
Enter fullscreen mode Exit fullscreen mode

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"})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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"),
]
Enter fullscreen mode Exit fullscreen mode

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:

  1. Gunicorn installed
poetry add gunicorn
Enter fullscreen mode Exit fullscreen mode
  1. collect all static files
poetry run python manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode
  1. configure the allowed hosts
#myapp/settings.py
# change ALLOWED_HOSTS = [] to
ALLOWED_HOSTS = ["localhost"]
Enter fullscreen mode Exit fullscreen mode

Now you are ready to run

poetry run gunicorn myapp.wsgi
Enter fullscreen mode Exit fullscreen mode

Go to localhost:8000 and enjoy your gunicorn served app

Conclusions

In this quite long walkthrough, we saw different things:

  1. set up a Django app with my personal useful tweaks
  2. set up an integrated Svelte app served by Django
  3. configure Django built-in authentication to secure our SPA
  4. providing some Rest API for the SPA to consume
  5. 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.

💖 💪 🙅 🚩
besil
besil

Posted on December 10, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related