Kanban Board

Get organised. Be more productive.

Table of Contents

For every project I add to Grok My Code, I provide extra information on design decisions, code snippets, technologies used or anything of interest related to the development.

Tech used on this project:

Python Django Django Rest Framework React Redux Javascript PostgreSQL
HTML
CSS
Nginx
Gunicorn
Linux

1. Overview

Kanban boards aid in the productivity of individuals and teams.

I created this app using a completely decoupled front and back end with React and Redux on the front end connecting to a Django Rest Framework API on the back end.

As with all my projects, a test driven development approach was followed.

A few key features include:

  • Multiple boards per user.
  • Column and card (task) creation.
  • Dragging of cards between columns.
  • Dragging of cards within a column to sort / organise them.

Source code and live demo:

GitHub

kanban.grokmycode.com


If you're testing the functionality of the demo site, you can either create your own user or simply use the following demo credentials:

Username: demo-user

Password: demo-password-234

2. Single Page Apps and RESTfulness

With the increasing popularity of SPA's, back end web servers are often used primarily as an API. With that in mind, the Django Rest Framework works very well for the creation of a back end API requiring typical CRUD operations.

The issue comes when each request from the front end requires more than simple CRUD operations on a single object. SPA's are highly dynamic and interactive by design, resulting in the need to perform multiple database operations for each front end action.

Added to this issue is the fact that typical RESTFul back ends try conform to standard url naming conventions such as /api/cards/ and /api/cards/2/.

The Kanban board involves several cases where this issue arises. For example, the board stores the position of each card within its column, thereby allowing users to sort / organise their cards as they wish.

When a user deletes a card from the middle of a column, the backed needs to not only delete the database record, but also update the positions of the remaining cards.

There are several ways to handle this, with pro's and con's for each. The following table is just a brief overview and is not meant to cover every step, edge case or implementation detail:

Pros Cons
Perform 2 ajax requests. First send a DELETE request for the card. Upon success, send a PATCH request containing the remaining cards with their updated positions.
  • Each request is RESTFul
  • Standard urls and HTTP verbs can be used
  • Multiple ajax requests for what should be a single atomic action.
  • If an error occurs after the first (DELETE) request such as a disconnect, the client and server are both left in an invalid state.
  • Business / Validation logic is being handled on the front end (ie: position ids) which is open to manipulation.
Perform 1 ajax request with all required deletions / updates json encoded in the HTTP data.
  • Single ajax request.
  • The operation is atomic.
  • The back end is not being used in a strictly RESTFul manor by performing updates / deletes across different objects in one request.
  • Non standard url may be needed. Non standard use of HTTP verbs.
  • Business / validation logic is being handled on the front end (ie: position ids) which is open to manipulation.
Perform 1 ajax DELETE request and let the back end figure out what else needs to happen. The response will contain all relevant updates for the remaining cards.
  • Single ajax request.
  • The operation is atomic.
  • No business / validation logic is handled on the front end.
  • The back end is not being used in a strictly RESTFul manor by performing updates / deletes across different objects in one request.
  • Non standard use of HTTP verbs.

In the world of SPA's, the web server API is not used as a RESTFul service in the true sense of the term. The back end is not being used by 3rd parties as an API for their own uses, but is instead being used to drive a dynamic front end.

I believe the 3rd option above is generally the way to go when complex non standard CRUD actions are required.

There is is always a risk of front end data manipulation and ultimately the back end is where the final business logic / validation occurs.

With that in mind, I used the 3rd approach above for deleting cards on the kanban board as follows:

  1. The front end sends a single DELETE request for the specified card while displaying a spinner.
  2. The back end deletes the specified card.
  3. The back end updates the positions for the remaining cards in the column.
  4. A response is sent to the front end containing the updated positions.
  5. The relevant cards in the Redux store are updated with the response data.

3. DRF: Updating multiple objects

Django Rest Framework (DRF) provides multiple object creation out of the box through the many=True argument when instantiating a ListSerializer.

The behavior of the object creations can be customised by implementing a create() method in the serializer.

When updating multiple objects, DRF does not provide default behavior since the expected behavior for every update is not always the same (ie: there is no safe default use case).

Therefore when updating multiple objects, a custom update() method must be implemented on the serializer.

The following snippet is my implementation of a custom multi object update() method for Kanban cards (eg: when their positions change).

4. Spinners and responsiveness in a SPA

Single Page Apps are largely about responsiveness and user interactivity. With this in mind, there are decisions to be made with respect to when the user interface should be updated in response to user actions.

This is especially important to consider when user actions affect the back end. You have the choice to either update the UI immediately before the back end has been updated, or display a front end spinner until a response has been received from the back end.

If the UI is updated immediately, the user gets a very snappy, responsive experience however you risk violating the Principle of least astonishment.

An example of this would be a user deleting a UI component only to see it magically reappear soon after if there is a problem with the request.

A better approach in my opinion is to use spinners (for almost all) user actions that require back end communication. The UI is updated immediately with a spinner, informing the user that something is happening. When the back end response is received, the UI is updated accordingly.

The following Javascript snippet illustrates using this technique when deleting a Kanban card and updating the Redux store accordingly.