The Role of Python in Single-Page Applications
Contents
A Single-Page Application (SPA) is an excellent choice for certain types of web applications. It can provide a more “desktop-like” user experience than traditional multi-page applications, while still using the cross-platform environment of a web browser. Although most modern SPA client-sides are built in JavaScript (jQuery, AngularJS, React, Vue.js, etc), there are many programming language options for the server-side code, for example Java, Ruby or JavaScript. But the readability and maintainability of Python makes it an attractive choice. In this post, I will explain what a SPA is, and give an introduction to the architecture and techniques used to create a SPA. I will also show an example “Movie Gallery” application using Python (Django Web framework) as the server-side language.
What is a SPA?
A single-page application (SPA) is a web application that loads a single HTML page and dynamically updates that page as the user interacts with the application. It differs from a traditional multi-page website in that upon clicking a link, only the section of the page that changes is retrieved from the server. This allows for a more fluid user experience as the browser does not have to render the full page. The classic example of a SPA is Google’s Gmail – on clicking to open an email, the full page does not need to reload.
In Fig. 1 the lifecycle differences between a traditional web application and a SPA are shown. When the client, the browser, requests page content in a traditional web app then the response is a full HTML page. Whereas with a SPA, the HTML page is downloaded to the client in the initial request and after that, only data is sent and received during business transactions. This is always done asynchronously through AJAX, which is the core of SPAs.
AJAX (Asynchronous JavaScript And XML) allows a means of unobtrusively requesting data from the server combined with JavaScript’s power to update the HTML DOM (Document Object Model). Although XML is in the AJAX name, this is quite misleading, as the data exchange format is typically JavaScript Object Notation (JSON). “Partial” HTML may also be retrieved from the server. AJAX first appeared in the early 2000s when the XMLHttpRequest (XHR) API was officially adopted by major browser vendors.
There are many advantages of a SPA over a traditional web application. These include:
- User experience: A SPA has the ability to redraw portions of the screen dynamically, and the user sees the update almost instantly as there is no need to retrieve a new page from the server. This leads to an increase in page speed (after the initial page load) and less “flicker” during the page transitions.
- Decoupled presentation layer: The client side controls how the User Interface appears and behaves. This allows the server-side to be maintained and tested separately. This also allows the same server-side code to be used for mobile applications.
The main disadvantages of SPA compared to a traditional website are:
- Search Engine Optimisation (SEO): A web crawler, which is used by search engines to index websites, will have more difficulty finding all of your pages if they are loaded dynamically. However Google and other search engines have reportedly developed web crawlers which tackle this issue.
- Strong JavaScript Dependency: SPAs require the user to have JavaScript enabled in their browser.
Designing a SPA
Despite the naming, there are multiple page “views” in a SPA. The Web page is divided up into logical zones, or regions which have an associated view. An example of which is shown in Fig. 2 where the screen space is divided into three areas. The “Content” region may be updated on a certain click, but the “Header” and “Sidebar” regions stay constant. This is the power of AJAX – it allows data to be fetched and part of the page to be repainted, while the rest of the screen stays the same.
The initial “shell” index.html is loaded and then, depending on how the user interacts with the website, different content is shown in certain areas. In Fig. 3, View 1 (for example the “Home” page) is swapped out with View 2 (for example the “About” page) to make a more seamless experience. Each page is placed inside the “container” div tag, and the container div is constant, but the content inside it changes.
The initial page, normally index.html, is important as it is the page that needs to include the resources for displaying the other pages. For a SPA, this can be quite simple. Generally just the parts that will be used to display the other pages are defined, which includes the CSS at the top and the JS scripts at the bottom of index.html.
Python on the Web
Many Web companies make use of Python including Instagram, Pinterest, YouTube and Eventbrite. These companies chose Python for their site infrastructure due to it’s simplicity and scalability. There are a number of Python Web application frameworks – Django and Flask are but two of a large number of options. These frameworks handle common operations in Web development including accessing the database, session management and creating HTML templates. Furthermore libraries such as Django REST framework, which provides a flexible toolkit for building Web APIs, enhance the functionality of individual frameworks.
In addition to the different web frameworks, the large number of Python built-in functionality and libraries make it possible to use tools for creating PDFs (Reportlab), creating CSV/Excel files, sending emails, queuing long-running tasks (Celery) and doing more complex server-side calculations (NumPy, etc). Fig. 4 shows examples of these frameworks and libraries.
Recently, Websockets have become available to frameworks built in Python. Websockets allow interactive communication between client and server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. The exciting “Channels” project has been developed under the Django repository but aims to provide functionality to other Python Web frameworks.
Movie Gallery Application
To illustrate the concepts above, I have written a “Movie Gallery” application available on Github here. This application shows a list of movies along with some basic information and a rating. For authenticated users, it is possible to edit and delete your movies and also add new movies. The application is built with an AngularJS client, however this could be changed to another client-side framework such as React or Vue.js. AngularJS queries the Python API for all the data needed.
In Fig. 5 the completed “Movie Gallery” application is shown. As detailed above, I have divided the screen into manageable regions. The “Footer” region stays constant throughout the application. The “Header” region is updated when the website user logs in or logs out by including the user’s name in the title. The “Content” region changes the most as this is the region where the movie list is viewed and also where the movie can be shown in more detail or edited. Only movies that the website user has previously added can be edited or deleted.
In Fig. 6 the “About” page is shown. As shown only the content region is updated with new content. The Header and Footer regions remain the same as in Fig. 5.
Python API
For the remainder of this post, the Python code (server-side) needed in this SPA is described. In order to retrieve the data shown on the client, and send data to the server, an API (Application Programming Interface) is set up. REST (REpresentational State Transfer) is an architectural style based on HTTP (Hypertext Transfer Protocol) for providing standards between computer systems on the Internet. Endpoints (URLs) define the structure of the API and are organised around resources. In total, 10 API endpoints are needed for the Movie Gallery application RESTful API as shown in Fig. 7. The APIs numbered 1-5 below are used to perform Get and CRUD (CReate, Update, Delete) operations on the movies database, to allow a user with the correct permissions to make changes to their movies. API no. 6 is used to get the list of movie genres. APIs numbered 7-10 are used for authentication – logging the user in/out and get information on the current user.
In order to implement what was needed, Django and Django REST framework were used to build the RESTful API. To handle the Django REST framework authentication a Python library called django-rest-auth was used.
As with any Django project the interactions with the databases are handled in the models.py file. In the code below each movie has a title, created date, summary, release year, movie director, the movie genre, a movie image, a user rating and the person who uploaded the movie. These are created as normal in Django using the appropriate model fields and a database migration. Also created was a separate model to hold the movie genres (Comedy, Horror, etc).
from django.core.validators import MaxValueValidator from django.db import models from django.contrib.auth.models import User class MovieGenre(models.Model): name = models.CharField(max_length=100, unique=True) class Movie(models.Model): title = models.CharField(max_length=100, unique=True) created_date = models.DateTimeField(auto_now_add=True) summary = models.TextField(max_length=400, blank=True, default='') release_year = models.PositiveIntegerField(default=2016) director = models.CharField(max_length=100, blank=True, default='') genre = models.ForeignKey(MovieGenre, related_name='movie_genre', default=1) image = models.ImageField(upload_to='movies/', default='movies/Movie.jpg') rating = models.PositiveIntegerField(validators=[MaxValueValidator(5)], default=3) user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
Next the serializers.py file was used to expose to the API the necessary fields. A serializer gives access through the API to the model. The main functionality needed is the ability to view the list of movies. For this line 15 in of the code below is used to expose the model fields. There is no need for the Movies Genres or the User models to be editable by the user through the API, so they are marked as read_only in lines 10 and 11.
from rest_framework import serializers from .models import Movie, MovieGenre class MovieGenreSerializer(serializers.ModelSerializer): class Meta: model = MovieGenre fields = ('id', 'name') class MovieSerializer(serializers.ModelSerializer): genre_obj = MovieGenreSerializer(source='genre', read_only=True) user_obj = UserSerializer(source='user', read_only=True) class Meta: model = Movie fields = ('id', 'title', 'summary', 'release_year', 'director', 'genre', 'genre_obj', 'image', ‘rating', 'user', 'user_obj')
Next the “root” URLs in the spa_movies/urls.py file were included. If a user accesses example.com, the movies/index.html file is to be shown. This is the only full HTML file in the application. To access the API a URL of /api/ is defined. To handle the authentication, the rest-auth API is defined with URLs provided by that library at /rest-auth/. Lastly to handle the user uploaded files (media), such as the movie images, a further URL is defined on line 11.
from django.conf import settings from django.conf.urls import url, include, static from django.views.generic import TemplateView from movies.urls import router urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='movies/index.html')), url(r'^api/', include(router.urls)), url(r'^rest-auth/', include('rest_auth.urls')) ] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
The URLs for the specific movie operations are defined in a separate file movies/urls.py. In this file the “DefaultRouter” class was used to create standard APIs for the Movies and MovieGenre models. The viewsets which are created in the next code block are registered to give consistent URL configuration throughout the application.
from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter(trailing_slash=False) router.register(r'movies', views.MovieViewSet) router.register(r'movies-genres', views.MovieGenreViewSet)
Finally the views.py file is added to bring the logic together. Viewsets allow combining the logic for different views in one class. In the code below the movie viewset uses the movie model (line 15) and the movie serializer (line 16). It should also require that the movie can only be changed by an authenticated user who is also the owner, otherwise read only access (line 14). The movies genres are exposed in a similar manner.
from rest_framework import viewsets from rest_framework.decorators import permission_classes from rest_framework.permissions import IsAuthenticatedOrReadOnly from .permissions import IsOwnerOrReadOnly, IsReadOnly from .serializers import MovieSerializer, MovieGenreSerializer from .models import Movie, MovieGenre class MovieGenreViewSet(viewsets.ModelViewSet): permission_classes = (IsReadOnly, ) queryset = MovieGenre.objects.all().order_by('name') serializer_class = MovieGenreSerializer class MovieViewSet(viewsets.ModelViewSet): permission_classes = (IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly) queryset = Movie.objects.all().order_by('title') serializer_class = MovieSerializer
At this point, the Python API has been built which has all the functionality needed by the client-side.
Testing
Code without tests is broken by design
– Jacob Kaplan-Moss, one of original Django developers
To understand the API, the built-in Django REST framework Web browsable API can be useful. In order to do automated testing, one of the nicest features of the using Django on the server-side is that testing in Python is relatively simple. The “APIClient” class in the Django REST framework test module is imported and used to run tests against the different functionality expected from the server. As part of the tests.py file, in the code below, the test checks that the API is successfully able to create a movie added by a newly-created user. A POST request is sent to /api/movies with the new movie detail in JSON.
from rest_framework.test import APIClient from movies.models import Movie class MovieTestCase(TestCase): def test_add_movie(self): user = User.objects.create_user('admin', 'myemail@test.com', 'password123') client = APIClient() client.login(username='admin', password='password123') self.assertEqual(len(Movie.objects.all()), 0) response = client.post('/api/movies', {'title': 'Lion King', 'summary': 'Lion Movie', 'release_year': '1994', 'rating': 2, 'director': 'Roger Allers'}) self.assertEqual(response.status_code, 201) self.assertEqual(len(Movie.objects.all()), 1)
Summary
In this blog post, it was explained what an SPA is, and the advantages and disadvantages compared to a traditional multi-page application. Some concepts such as AJAX and dividing the screen into regions were explained. The significant role that Python plays in web applications was shown, as was the architecture of setting up a REST API that can be used as part of building a SPA.
An example “Movie Gallery” application was shown. The server-side (Python) portion of this SPA was described in detail. The files models.py, views.py, serializers.py and urls.py are the main files that control the entire Python API. With a small number of lines, a powerful API was built which leverages many of Python’s built-in functionality and libraries. The client-side part of the SPA still needs to be developed in JavaScript, but Python can used to provide maintainable and testable server-side code. The entire SPA code can be downloaded from the Github account and run on your own machine.
References
1. SPA Design and Architecture. Understanding single-page web applications by Emmit A. Scott, Jr.
2. Sitepoint: Creating a CRUD App in Minutes with Angular’s $resource
3. Single-Page Applications: Build Modern, Responsive Web Apps with ASP.NET