Angular API Calls with Django, Part 2: Building the Micro-Blog App
This is the second part of a two-part series exploring the use of the Angular 6 HttpClient to make API calls against a Django back-end using Django...
Curious about how to make API calls with Angular 6 and the HttpClient service? This tutorial will show you some techniques for building a decoupled micro-blogging application using Angular 6 and the Django Rest Framework (DRF). Along the way, we will learn the following
Metal Toad is an AWS Managed Services provider. In addition to Angular work we recommend checking out our article on how to host a website on AWS in 5 minutes.
Curious about how to make API calls with Angular 6 and the HttpClient service? This tutorial will show you some techniques for building a decoupled micro-blogging application using Angular 6 and the Django Rest Framework (DRF). Along the way, we will learn the following:
Ready? Let's get started!
The source code for this tutorial can be found on GitHub at https://github.com/kdechant/angular-django-example.
The example code requires Python 3 (Django 2.x requires Python 3.4 or higher) and uses a SQLite database, so install should be easy and painless.
My previous tutorial will give you a basic understanding of how to make API calls using Angular.
If you're new to Django and DRF, you can find some useful tutorials at the Django project and the Django Rest Framework site. This example will build upon the basic knowledge from those tutorials.
Some knowledge of pip and Python Virtual Environments will also be useful here. Be aware that we're using classic virtualenv here and not the newer pipenv yet.
The demo shown here uses the following technology:
Why these platforms? Angular, because it's a full-featured front-end framework that has tremendous popularity. Django and Django Rest Framework, to provide the ORM and API layer. Python is growing in popularity and is an enjoyable language to develop applications.
The Django app is the back end of our decoupled application. It serves the API endpoints and it also renders the HTML container for the Angular front end app.
We will dive more deeply into DRF and its models, serializers, and viewsets in a later part of this tutorial. For now, we have a simple Django project with Django Rest Framework and the Django Rest Framework JWT packages installed.
pip install Django pip install djangorestframework djangorestframework-jwt.
What these do:
The vanilla install of Django provides a basic settings file for the application. To activate DRF and the JWT extension, we need to add DRF to our installed apps, and configure its settings:
angular_django_example/settings.py:
INSTALLED_APPS = [ ... 'rest_framework', ] REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), }
By default, DRF allows Basic and Session authentication. The DEFAULT_AUTHENTICATION_CLASSES setting adds a third authentication mechanism, the JWT.
How do these work?
In addition to the Rest Framework configuration, the JWT package also has its own configuration settings. We will update two settings in particular for our app.
angular_django_example/settings.py
JWT_AUTH = { 'JWT_ALLOW_REFRESH': True, 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600), }
JWT tokens have a life span, after which they are no longer valid. The default is only 5 minutes, but we can set it to a longer time (say, 1 hour) using the JWT_EXPIRATION_DELTA setting. The JWT_ALLOW_REFRESH setting enables a feature of DRF-JWT where an application can request a refreshed token with a new expiration date.
In addition to the settings, we need to add a few URLs to our API:
angular_django_example/urls.py:
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token urlpatterns = [ ... other patterns here ... path(r'api-token-auth/', obtain_jwt_token), path(r'api-token-refresh/', refresh_jwt_token), ]
These endpoints provide us with a means to authenticate via the API and to request a new token.
Now that our application-wide settings are configured, we can create the Django app and the Angular app within it.
From the terminal, we can run python manage.py startapp microblog
to create our new Microblog app. This gives us an empty Django app with the usual views.py, models.py, urls.py, and so on.
We now need to create a simple View and a template which will serve the single-page app.
microblog/views.py:
from django.shortcuts import render def index(request, path=''): """ The home page. This renders the container for the single-page app. """ return render(request, 'index.html')
In Django fashion, we will use two template files, base.html, providing the outer HTML shell, and index.html, providing the content of the index page itself.
microblog/templates/base.html:
{% load staticfiles %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Angular, Django Rest Framework, and JWT token demo</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous"> </head> <body> <div class="container"> {% block heading %} <h1>Angular, Django Rest Framework, and JWT demo</h1> {% endblock %} {% block content %}{% endblock %} </div> </body> </html>
microblog/templates/index.html:
{% extends "base.html" %} {% load staticfiles %} {% block content %} <p>This is a mini-blog application using a back-end built on Django 2.0 and Django Rest Framework. It illustrates how to create and send JSON Web Token authentication headers with the HTTP requests.</p> <app-root>Loading the app...</app-root> <script type="text/javascript" src="{% static 'front-end/runtime.js' %}"></script> <script type="text/javascript" src="{% static 'front-end/polyfills.js' %}"></script> <script type="text/javascript" src="{% static 'front-end/styles.js' %}"></script> <script type="text/javascript" src="{% static 'front-end/vendor.js' %}"></script> <script type="text/javascript" src="{% static 'front-end/main.js' %}"></script> {% endblock %}
The <script>
tags in index.html will load the compiled JavaScript files generated by webpack. We'll need to change a setting in angular.json to make this work, which we'll see momentarily.
For the first part of this tutorial, we are using only built-in models (like django.contrib.auth.models.User) and the Views that are provided by DRF and the JWT extension. In Part 2, we will delve into creating the rest of our micro-blogging application. There, you will see custom models, serializers, and views.
To install an Angular app within a Django project, we just place the TypeScript source code inside of our "microblog" Django app.
cd microblog
ng new front-end
This will generate a starter Angular app in microblog/front-end, with the following interesting files:
Here we have our first conflict between the "Django way" and the "Angular way". Django's built-in "staticfiles" app won't know to look in microblog/front-end/dist to find the compiled JavaScript files and other assets. Django wants the static files for each app to live in a subdirectory called "static". So, we need to edit our angular.json file to instruct Angular CLI to place the files there.
microblog/front-end/angular.json:
{ ... "projects": { "ng-demo": { ... "architect": { "build": { ... "options": { "outputPath": "../static/front-end", <-- change this line ...
Now, when we run ng build
it will place the files right where Django wants them.
The Angular app we're creating here will contain the following pieces:
Let's start building the app by configuring our module file.
microblog/front-end/src/app/app.module.ts:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; // add this import { FormsModule } from '@angular/forms'; // add this import { AppComponent } from './app.component'; import { UserService } from './user.service'; // add this @NgModule({ declarations: [AppComponent], imports: [BrowserModule, FormsModule, HttpClientModule], // add this providers: [UserService], // add this bootstrap: [AppComponent] }) export class AppModule { }
This is very close to the default Angular module file. We have only added the built-in HttpClientModule and FormsModule, as well as our custom UserService, which we'll build in a minute.
Our app is very simple and has just one Component:
microblog/front-end/src/app/app.component.ts:
import {Component, OnInit} from '@angular/core'; import {UserService} from './user.service'; import {throwError} from 'rxjs'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { /** * An object representing the user for the login form */ public user: any; constructor(private _userService: UserService) { } ngOnInit() { this.user = { username: '', password: '' }; } login() { this._userService.login({'username': this.user.username, 'password': this.user.password}); } refreshToken() { this._userService.refreshToken(); } logout() { this._userService.logout(); } }
Our template contains a login form, and a message to the user once they have logged in. We can also spruce it up a little with some Bootstrap classes.
microblog/front-end/src/app/app.component.html:
<h2>Log In</h2> <div class="row" *ngIf="!_userService.token"> <div class="col-sm-4"> <label>Username:</label><br /> <input type="text" name="login-username" [(ngModel)]="user.username"> <span *ngFor="let error of _userService.errors.username"><br /> {{ error }}</span></div> <div class="col-sm-4"> <label>Password:</label><br /> <input type="password" name="login-password" [(ngModel)]="user.password"> <span *ngFor="let error of _userService.errors.password"><br /> {{ error }}</span> </div> <div class="col-sm-4"> <button (click)="login()" class="btn btn-primary">Log In</button </div> <div class="col-sm-12"> <span *ngFor="let error of _userService.errors.non_field_errors">{{ error }}<br /></span> </div> </div> <div class="row" *ngIf="_userService.token"> <div class="col-sm-12">You are logged in as {{ _userService.username }}.<br /> Token Expires: {{ _userService.token_expires }}<br /> <button (click)="refreshToken()" class="btn btn-primary">Refresh Token</button> <button (click)="logout()" class="btn btn-primary">Log Out</button> </div> </div>
When the user first arrives on the page, they will see the login form. Once they have successfully logged in, they will see a welcome message and their username. For the purposes of this demo app, we will also output the expiration time of their JWT authentication token.
Now for what you're been waiting for: the User Service, which handles all the Angular API calls we need to authenticate a user and manage their JWT tokens.
microblog/front-end/src/app/user.service.ts:
import {Injectable} from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; @Injectable() export class UserService { // http options used for making API calls private httpOptions: any; // the actual JWT token public token: string; // the token expiration date public token_expires: Date; // the username of the logged in user public username: string; // error messages received from the login attempt public errors: any = []; constructor(private http: HttpClient) { this.httpOptions = { headers: new HttpHeaders({'Content-Type': 'application/json'}) }; } // Uses http.post() to get an auth token from djangorestframework-jwt endpoint public login(user) { this.http.post('/api-token-auth/', JSON.stringify(user), this.httpOptions).subscribe( data => { this.updateData(data['token']); }, err => { this.errors = err['error']; } ); } // Refreshes the JWT token, to extend the time the user is logged in public refreshToken() { this.http.post('/api-token-refresh/', JSON.stringify({token: this.token}), this.httpOptions).subscribe( data => { this.updateData(data['token']); }, err => { this.errors = err['error']; } ); } public logout() { this.token = null; this.token_expires = null; this.username = null; } private updateData(token) { this.token = token; this.errors = []; // decode the token to read the username and expiration timestamp const token_parts = this.token.split(/\./); const token_decoded = JSON.parse(window.atob(token_parts[1])); this.token_expires = new Date(token_decoded.exp * 1000); this.username = token_decoded.username; } }
Remember when we installed djangorestframework-jwt that we added two URLs to our urls.py, "/api-token-auth" and "/api-token-refresh"? Here we use Angular's HttpClient service to send POST requests to them.
The UserService's login() method sends the username and password to "/api-token-auth" and receives a token in response. If the login is unsuccessful, we receive some error messages (this.errors) which will be shown to the user within the template.
The UserService's refreshToken() method sends the current token (not the username and password) to the "/api-token-refresh" endpoint. This retrieves a new token for the same user, with a new expiration time.
So, what's in a JWT token? The API endpoints will return something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE1MjgwNjg4NDEsInVzZXJfaWQiOjEsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTUyODA3MjQ0MX0.oxqzt-d2l5Bl473KwObsUetlKS2uOMXn7vqcHcSX5Gg
That looks frightening, but it is really just a series of base64-encoded strings glued together. The data payload is stored in JSON format within the second of these strings, between the first and second dot.
We can split by the dot, then run the built-in JavaScript method window.atob()
and JSON.parse()
on the second result, to get our token payload:
const token_parts = this.token.split(/\./); const token_decoded = JSON.parse(window.atob(token_parts[1])); console.log(token_decoded); // output: // { // "orig_iat": 1528071221, // "exp": 1528074821, // "username": "user1", // "email": "user1@example.com", // "user_id": 2 // }
This contains the username as well as the user's email address and numeric ID from the database. It also contains the expiration time as a Unix timestamp. In the code above, we used this.token_expires = new Date(token_decoded.exp * 1000);
to convert this into a JavaScript Date object which provides a nicer display for the user.
Now that we have a means for our users to log in, it's time to build the rest of the Microblog app. The Django models and custom API endpoints will be covered in a future post, but for now, here's a teaser:
microblog/front-end/app/blog_post.service.ts:
import {Injectable} from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; import {UserService} from './user.service'; @Injectable() export class BlogPostService { constructor(private http: HttpClient, private _userService: UserService) { } // send a POST request to the API to create a new blog post create(post, token) { let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': 'JWT ' + this._userService.token }) }; return this.http.post('/api/posts', JSON.stringify(post), httpOptions); } }
Notice how we send the JWT token in the Authorization header on our API call. This will authenticate the user with each request and will allow Django to know which user made the request.
Continue to Part 2 of this series to see the rest of the micro-blogging app.
Looking for more examples of Angular API calls using HttpClient and ForkJoin? See how I used Angular, Django Rest Framework, HttpClient, and ForkJoin to rebuild a classic text adventure game.
For the front-end developers, you may be interested in some examples of Angular animations.
Happy coding!
This is the second part of a two-part series exploring the use of the Angular 6 HttpClient to make API calls against a Django back-end using Django...
With the release of Angular 6.0 in May 2018, the framework has been updated to depend on version 6.0 of the RxJ
Angular 4.3 introduced a new HttpClient service, which is a replacement for the Http service from Angular 2. It works mostly the same as the old...
Be the first to know about new B2B SaaS Marketing insights to build or refine your marketing function with the tools and knowledge of today’s industry.