Separating Frontend from Backend with Angular and Django

Published on

At web development, things evolve and grow in complexity faster than we can follow. That old way of creating websites where the pages were built only with HTML and if you had to change something you had to go through many pages changing the same thing is something rare nowadays. Now, even to create static websites, we use tools to optimize our work.

On the last years, we had an increase in javascript frameworks and an excellent evolution on the frontend as a whole. These new technologies have their tools and pipelines that makes harder to a single system to deliver both frontend and backend.

At the same time, the API architecture is becoming more popular, bringing the idea of giving more autonomy to the teams and making the applications more scalable.

However, the first try to implement this architecture can be hard, and many questions around that can pop out.

On this post, we’ll see a simplified example to help us to put this concept in practice. We’ll create an application of shopping list where we can add, retrieve, and exclude the items on the list. To do that we’ll use Django 2 for the backend and Angular 6 for the frontend.

The repositories with the code for this are:

Splitting the Responsibilities

To start, let’s talk a little about the responsibilities divided through the project.

There are two significant responsibilities inside an interactive web application: the management and the visualization of the data.

Backend

Looking to the client/server architecture, we call as the backend, the layer responsible for managing the access on the data, and its processing happens entirely on the server that is responsible for the service. The backend is responsible for accessing, processing, storing, and controlling the access to the data requested by the client. The most famous implementations of it are the REST API’s.

Frontend

On the other hand, the frontend is responsible for the presentation layer of your application, and most of the work usually happens on the client-side.

Backend Implementation

Now that we have the responsibilities divided, let’s start by writing the backend.

First, install Django and create a new project:

$ pip install django
$ django-admin startproject backend
$ cd backend
$ python manage.py migrate
$ python manage.py runserver

Then we start the browser at localhost:8000 to see the Django new application success screen.

The next step is to install the Django Rest Framework:

$ pip install djangorestframework

After that we edit the settings.py file to plug it:

INSTALLED_APPS = (
    ...
    'rest_framework',
)

At urls.py we add the framework authentication URLs to get access to the API interactive navigation.

from django.urls import include, path

urlpatterns = [
    ...
    path('api-auth/', include('rest_framework.urls')),
]

Now it is possible to log in by accessing localhost:8000/api-auth/login/.

rest login

With the authentication ready, let’s start the shopping list app:

python manage.py startapp shopping

The application will be pretty straight forward with a single model that will represent the shopping list at the database, a serializer that will transform the data from the Python object into JSON and handle validation, and a view that will receive parse and answer the incoming requests:

# settings .py

INSTALLED_APPS = (
    ...
    'shopping',
)

# models.py

from django.db import models


class ShoppingItem(models.Model):

    name = models.CharField(max_length=60)
    quantity = models.PositiveSmallIntegerField()
    checked = models.BooleanField(default=False)

# serializers.py

from rest_framework import serializers

from .models import ShoppingItem


class ShoppingItemSerializer(serializers.ModelSerializer):

    class Meta:
        model = ShoppingItem
        fields = '__all__'

# views.py

from rest_framework import viewsets

from .models import ShoppingItem
from .serializers import ShoppingItemSerializer


class ShoppingItemViewSet(viewsets.ModelViewSet):

    serializer_class = ShoppingItemSerializer
    queryset = ShoppingItem.objects.all()

# urls.py

from django.urls import include, path
from rest_framework import routers

from shopping.views import ShoppingItemViewSet

router = routers.DefaultRouter()
router.register(
    'shopping-item', ShoppingItemViewSet, base_name='shopping-item'
)


urlpatterns = [
    ...
    path('', include(router.urls)),
]

Now, we need to create the database migrations and apply that to our database:

$ python manage.py makemigrations
$ python manage.py migrate

And voilá, our backend is working:

shopping list

Frontend Implementation

To start the frontend, let’s install the CLI tools to create an Angular project:

$ npm install -g @angular/cli
$ ng new frontend
$ cd frontend
$ ng serve

Now we can access the frontend at localhost:4200 to see the Angular new project page.

angular home

Then we create a service to access the API and a view to list the items at the shopping list:

// shopping-item.interface.ts

export interface ShoppingItem {
  id: number;
  name: string;
  quantity: number;
  checked: boolean;
}

// api.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ApiService {

  private apiRoot = 'http://localhost:8000/';

  constructor(private http: HttpClient) { }


  getShoppingItems() {
    return this.http.get(this.apiRoot.concat('shopping-item/'));
  }
}

//  app.component.ts

import { Component, OnInit } from '@angular/core';

import { ApiService } from './api.service';
import { ShoppingItem } from './shopping-item.interface';

@Component({
  selector: 'app-root',
  template: `
  <div style="text-align:center">
    <h1>
      Shopping list
    </h1>
  </div>
  <ul>
    <li *ngFor="let item of items">
      <h2>{{ item.name }}</h2>
    </li>
  </ul>
  {{ error?.message }}
  `
})
export class AppComponent implements OnInit {

  items: ShoppingItem[];
  error: any;

  constructor(private api: ApiService) { }

  ngOnInit() {
    this.api.getShoppingItems().subscribe(
      (items: ShoppingItem[]) => this.items = items,
      (error: any) => this.error = error
    );
  }
}

// app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { ApiService } from './api.service';
import { AppComponent } from './app.component';


@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
  ],
  providers: [
    ApiService,
  ],
  bootstrap: [
    AppComponent,
  ],
})
export class AppModule { }

Accessing localhost:4200 we can see our beautiful shopping lis…

ops

wtf

There is something wrong with our application, let’s check the browser console:

cors error

Ding! Ding! Ding! It seems we found our problem!

sherlock holmes

Cross-Origin Resource Sharing (CORS)

For security reasons browsers implement the same origin policy, that blocks scripts that make HTTP requests with cross-origin, which is the requests that have protocol, domain, or port that differs from its origin.

To enable cross-origin requests safely the Cross-origin resource sharing (CORS) was created, and it allows the communication between the frontend and the backend at different sources. It is also helpful to enable requests for resources stored at a CDN.

The mechanism operates by adding new HTTP headers on the request to describe the group of origins allowed to access that resource through the browser. It also requires that the browsers “preflight” the requests that can cause collateral effects on the server (like POST, PUT, and DELETE) with the OPTIONS request who needs approval before sending the real action.

The browser (by consequence our frontend too) is ready to deal with CORS. We only need to implement the backend part.

$ pip install django-cors-headers

After the installation we need to add its configuration at the settings.py:

INSTALLED_APPS = (
    ...
    'corsheaders',
)

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]

CORS_ORIGIN_WHITELIST = (
    'localhost:4200',
)

At CORS_ORIGIN_WHITELIST we put only the origin that we want to allow access. On our case is localhost:4200, but on the production environment, it will be something like api.backend.com.

With that done, we can go to the browser and see the communication between the frontend and the backend working \o/.

it works

Then, to make it even cooler, let’s add some interactivity to our shopping list:

// api.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ApiService {

  private apiRoot = 'http://localhost:8000/';

  constructor(private http: HttpClient) { }


  getShoppingItems() {
    return this.http.get(this.apiRoot.concat('shopping-item/'));
  }

  createShoppingItem(name: string, quantity: number) {
    return this.http.post(
      this.apiRoot.concat('shopping-item/'),
      { name, quantity }
    );
  }

  deleteShoppingItem(id: number) {
    return this.http.delete(this.apiRoot.concat(`shopping-item/${id}/`));
  }
}

//  app.component.ts

import { Component, OnInit } from '@angular/core';

import { ApiService } from './api.service';
import { ShoppingItem } from './shopping-item.interface';

@Component({
  selector: 'app-root',
  template: `
  <div style="text-align:center">
    <h1>
      Lista de compras
    </h1>
  </div>
  <ul>
    <li *ngFor="let item of items">
      <h2>{{ item.quantity }}x {{ item.name }}
      <button (click)="delete(item.id)">x</button></h2>
    </li>
  </ul>

  <input #itemQuantity type='text' placeholder='Qtd'>
  <input #itemName type='text' placeholder='Name'>
  <button (click)="add(itemName.value, itemQuantity.value)">Add</button>
  {{ error?.message }}
  `
})
export class AppComponent implements OnInit {

  items: ShoppingItem[];
  error: any;

  constructor(private api: ApiService) { }

  ngOnInit() {
    this.api.getShoppingItems().subscribe(
      (items: ShoppingItem[]) => this.items = items,
      (error: any) => this.error = error
    );
  }

  add(itemName: string, itemQuantity: number) {
    this.api.createShoppingItem(itemName, itemQuantity).subscribe(
      (item: ShoppingItem) => this.items.push(item)
    );
  }

  delete(id: number) {
    this.api.deleteShoppingItem(id).subscribe(
      (success: any) => this.items.splice(
        this.items.findIndex(item => item.id === id)
      )
    );
  }
}

Now we have our shopping list done:

Conclusion

To split the frontend to the backend into different projects generates more work when you compare it to a monolith. But, after you get used to it, you will start to see the benefits that come from this separation like the flexibility, the liberty across the teams to use their own set of tools and pipelines, and the performance improvement by delegating some of the processing to the client-side. Also, if you are a company, you get the possibility to hire specialized to evolve your platform without requiring that everyone has a full-stack knowledge upfront.