In the third part of this series, I introduced Passport.js module and implemented User Authentication with NestJS, Passport.js, and JWT to secure the To Do REST API endpoints.

This article covers how you can connect an Angular front-end app with a NestJS back-end API. NestJS is no different than any other back-end technology in delivering REST APIs that are accessible by front-end apps including Angular, React,JS, Vue.JS, and other front-end frameworks.

I used Angular CLI to build an Angular v8 app. The app uses Bootstrap 4 for the layout and styling. By the end of the article, you will build something similar to what you see in Figure 1:

Figure 1: Todo Page
Figure 1: Todo Page

The application consists of:

  • A Login component for users to login to the app
  • A Home component to welcome users
  • A Todo Home component acting as a landing page to display todo items and Tasks
  • A Todo Create component allowing the user to create a new todo list item
  • A Todo List component to list all available todo lists in the database
  • A Task Create component allowing the user to create a new task under a specific todo list item
  • A Task List component to list all available tasks under a specific todo list item

NestJS is no different from any other back-end technology for building and providing REST APIs.

You'll be able to add new todo and task items, as well as delete existing items. I've left editing a todo list item and task item as an exercise to practice with Angular and NestJS.

Create Angular App

Start by creating a new Angular v8 app using the Angular CLI. Make sure you install the latest version of the Angular CLI using the following command:

npm install -g @angular/cli

Now that the Angular CLI is installed, let's create an application by following the steps:

  1. Navigate to the folder on your computer that holds the back-end NestJS source code.
  2. Side by side, issue the following command to create a new Angular app:
ng new todo-client

The Angular CLI prompts you with a set of questions to better customize the experience for your own needs and requirements. My answers to those questions are listed here:

  • Would you like to add Angular routing? Yes
  • Which stylesheet format would you like to use? SCSS

Right after that, the CLI starts generating the files and folders needed and installs the relevant NPM packages.

Now that the application is created, navigate inside the directory and make sure it has been generated successfully. Run the command:

ng serve --open

The command starts an internal Webpack Web server to serve the application and opens the browser automatically for you. You should be seeing something similar to Figure 2:

Figure 2: Angular CLI home page
Figure 2: Angular CLI home page

Great! The app is up and running.

Let's add the Authentication module to the application.

Add Auth Library

The Auth source code hosts all the components and services required by the application. Angular CLI offers two options to isolate your code:

  • An Angular module
  • An Angular library

With both, the Auth module lives in an isolated and self-contained environment. A library is more like a mini-app containing a module file, component, service, TypeScript config files, and above all, the public-api.ts TypeScript file. The public-api.ts file is used to specify all the library items that you wish to export and make visible and accessible to other libraries or modules in the application.

For this article, I've decided to make use of Angular Libraries.

Let's create the Auth Library by issuing the following command:

ng generate library auth

This command creates a new library named auth inside a folder located under the root-level projects folder. The Angular CLI places all libraries under this root-level projects folder. The Auth library generates an Auth module that can be imported by the main application module. Figure 3 shows the content of Auth library that you've just created:

Figure 3: Auth library
Figure 3: Auth library

Add Auth Service

The Auth service hosts the logic required to login and logout a user from the application. Start by generating a new service by running the command:

ng generate service services/auth --project=auth

The command scaffolds a new auth.service.ts file inside the services folder inside the auth library. Listing 1 defines the login() function.

Listing 1: Login method

login(username: string, password: string) 
{    
    return this.http.post<any>('/auth/login', { username, password }).pipe(map(user > {
        if (user && user.accessToken) {
            localStorage.setItem('currentUser', JSON.stringify(user));
            this.currentUserSubject.next(user);       
        }
        return user;
    }));
}

The code initiates a POST /auth/login request to the back-end REST API passing over the username and the password that the user has entered on the login form. If the back-end server successfully authenticates the user and returns a valid accessToken (JWT), the function:

  • Stores the user details including the JWT inside LocalStorage
  • Populates a private currentUserSubject with the user details

The service defines the currentUserSubject as a BehaviorSubject<ApplicationUser> instance. At any moment of time, you can reactively query this variable to return the currently authenticated user.

In addition, the service defines a public property named currentUserValue to hold the value of the currentUserSubject at any moment of time. Shortly, you will see how to make use of this property inside the Auth Guard.

To logout a user, the Auth service defines this function:

logout() {  
    localStorage.removeItem('currentUser');  
    this.currentUserSubject.next(null);
}

The function removes the user details from the LocalStorage and also resets the user details inside the currentUserSubject.

Add Auth Guard

The Auth guard decides if a user can access a requested Route. Depending on whether the user is authenticated or not, the Auth guard responds accordingly. Start by generating a new guard by running the following command:

ng generate guard auth --project=auth

This command prompts you with a question: Which interfaces do you want this new guard to implement?

  • CanActivate
  • CanActivateChild
  • CanLoad

These are three different guard strategies that you can implement in Angular depending on the use case. For this application, I've chosen the CanActivate option.

Listing 2 defines the Auth Guard. The CanActivate interface implementation starts by accessing the AuthService currentUserValue property.

  • If this property holds a valid user, it returns true. This means that you need to allow the user to access the Route.
  • Otherwise, redirect the user to the Login page and prevent them from accessing the current Route.

You will see shortly how to make use of this guard when you define the application routes.

Listing 2: Auth service

import { Injectable } from '@angular/core';
import {  ActivatedRouteSnapshot,  RouterStateSnapshot,  CanActivate,  Router} from '@angular/router';import { AuthService } from './services/auth.service';
@Injectable({ providedIn: 'root'})

export class AuthGuard implements CanActivate {
    constructor(private router: Router, private authService: AuthService) {}
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const currentUser = this.authService.currentUserValue;
        if (currentUser) {
            // logged in so return true
            return true;
        }
        
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });    
        
        return false;  
    }
}

Add Login Component

The user logs into the application using a Login page. This page communicates with the back-end API to authenticate the user, generate a new JWT and return results to the calling application. Start by generating a new Angular component by running the following command:

ng generate component components/login 
--project=auth --skipTests

This command scaffolds a new Login Angular component inside the components folder. In addition, the command declares this component at the Auth Module level.

Listing 3 defines the HTML template of the Login component. The component defines a form with two fields: username and password. This form is data-bound to the loginForm that you will define shortly inside the code-behind of this template. In addition, it adds error/validation UI to notify the user in case they enter a wrong username format or they miss entering one of the two fields. The template also adds a Submit button to allow the user to login to the application.

Listing 3: Master page

<div class="container my-3">  
  <div class="row text-center mb-5">    
    <div class="col-md-12 bg-light p-3">      
      <h2>Login</h2>    
    </div>  
  </div>
  <div class="row">    
    <div class="col-md-6 offset-md-3">      
      <form [formGroup]="loginForm" (ngSubmit)="onSubmit()" autocomplete="off">        
        <div class="form-group">          
          <label for="usernam">Username</label>          
          <input type="text" class="form-control" id="username" formControlName="username" placeholder="Username" />          
          <div *ngIf="submitted && f.username.errors">
            <small *ngIf="f.username.errors.required" class="form-text text-muted">Username is required</small>
          </div>
        </div>
        <div class="form-group">
          <label for="password">Password</label>          
          <input type="password" class="form-control" id="password" placeholder="Password" formControlName="password" />
          <div *ngIf="submitted && f.password.errors"> 
            <small *ngIf="f.password.errors.required" class="form-text text-muted">Password is required</small>
          </div>
        </div> 
        <div *ngIf="error" class="my-3">
          <small class="form-text text-muted">{{ error | json }}</small>
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>
    </div>
  </div>
</div>

Listing 4 defines the TypeScript code of the Login component.

Listing 4: Login component

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { first } from 'rxjs/operators';
import { AuthService } from '../../services/auth.service';

@Component({ templateUrl: 'login.component.html', styleUrls: ['login.component.css']})

export class LoginComponent implements OnInit {  
    loginForm: FormGroup;  
    submitted = false;  
    returnUrl: string;  
    error: string;
    
    constructor(private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private authService: AuthService) {}
    
    ngOnInit() {
        this.loginForm = this.formBuilder.group({ 
            username: ['bhaidar', Validators.required],
            password: ['@dF%^hGb03W~', Validators.required]
        });
        
        // reset login status    
        this.authService.logout();
        
        // get return url from route parameters or default to '/'    
        this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/';  
    }
    
    get f() { 
        return this.loginForm.controls; 
    }
    
    onSubmit() {
        this.submitted = true;
        
        // stop here if form is invalid    
        if (this.loginForm.invalid) {
            return;    
        }
        
        this.authService.login(this.f.username.value, this.f.password.value)
            .pipe(first())
            .subscribe(data => {
                this.error = '';
                this.router.navigate([this.returnUrl]);
            }, error => {
                this.error = error;
            });
    }
}
  • It starts by creating the loginForm instance and defining the fields under this form. In this case, only two fields are defined: username and password.
  • It then logs out the user just in case there was still an active session from previous logins.
  • Then it stores the returnUrl in a private variable to use later to redirect the user upon a successful login.

When the user tries to access a protected page and the Auth Guard redirects him to the Login page, it also appends to the Login page URL a query string named returnUrl to preserve the original page URL that the user was trying to access.

When the user submits the login form, Angular executes the onSubmit() function. This function starts by:

  • Making sure the form is valid.
  • It then calls the AuthService.login() function passing to it the username and password entered by the user.
  • If the back-end successfully validates the credentials and authenticates the user, the function then redirects the user back to the returnUrl page.
  • Otherwise, the component displays the user-friendly errors returned from the back-end API.

Add JWT Interceptor

In part three of this series, I secured the back-end REST API endpoints with JWT (JSON Web Tokens). This means that for every request you send to any of the secured API endpoints, you need to pass over a valid JWT in the request Header so that the back-end can verify your identity and allow you to continue accessing the API.

For this reason, I'll add and implement an Angular Interceptor that will perform a lookup prior to sending any request to the server. It checks if there is a valid user record stored in the Auth Service, extracts the token from the record, and adds a Bearer Authentication Header token onto the request. This is exactly what the back-end REST API is expecting to properly verify and authenticate the user request.

Listing 5 defines the JWT Interceptor.

Listing 5: JWT Interceptor

import { HttpInterceptor,  HttpRequest,  HttpHandler,  HttpEvent,  HTTP_INTERCEPTORS} from '@angular/common/http';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { AuthService } from './auth.service';

@Injectable() 

export class JwtInterceptor implements HttpInterceptor {  
    constructor(private authService: AuthService) {}
    
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // add authorization header with jwt token if available    
        const currentUser = this.authService.currentUserValue;    
        if (currentUser &amp;&amp; currentUser.accessToken) {      
            request = request.clone({
                setHeaders: {
                    Authorization: 'Bearer ${currentUser.accessToken}'
                }
            });    
        }
        return next.handle(request);  
    }
}

export const jwtInterceptorProvider = { provide: HTTP_INTERCEPTORS,  useClass: JwtInterceptor,  multi: true};
  • An Angular interceptor implements the HttpInterceptor.
  • It implements a single function named intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>.
  • The Angular infrastructure passes to the intercept() function the current HTTP Request and the next handler in the pipeline (could be another interceptor).
  • The function then accesses the currentUserValue property on the Auth Service.
  • If the property has a valid user and a valid access token, it clones the current request and sets the Authorization header to a Bearer {JWT} string.
  • Finally, it returns a call to the next.handle(request) so that other handlers in the request pipelines are executed all the way to reach the back-end REST API.

The last line in the listing defines an Angular Provider for this interceptor. You'll provide this interceptor in the App main module. This way, you ensure that any request the application sends to the back-end REST API has an Authorization header set properly.

Angular provides an extendible request pipeline that allows you to define and implement Request Interceptors that can act on a request before sending it over to the back-end server and after receiving a response from the same server.

Add Error Interceptor

The back-end REST API returns a 401 Unauthorized response when it can't verify and authenticate a user. It also returns other types of responses including 400 Bad Request, 403 Forbidden and others. The front-end application needs a way to capture the “bad” response from the back-end REST API and act.

For instance, if the back-end REST API returns a 401 Unauthorized response, this means that the application is requesting a secured API endpoint while the user is not logged in to the application (no Authorization Header) on the request. In this case, the front-end app should redirect the user to the Login page to authenticate themselves with the back-end REST API.

I'll add and implement a new Angular Interceptor to handle the error responses from the back-end REST API properly.

Listing 6 defines the ErrorInterceptor class.

Listing 6: Error Interceptor

import { Injectable } from '@angular/core';
import { HttpInterceptor,  HttpRequest,  HttpHandler,  HttpEvent,  HttpErrorResponse,  HTTP_INTERCEPTORS} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthService } from './auth.service';

@Injectable()

export class ErrorInterceptor implements HttpInterceptor {  
    constructor(private authService: AuthService) {}
    
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {    
        return next.handle(request).pipe(catchError((err: HttpErrorResponse) => {
            if (err.status === 401 && !window.location.href.includes('/login')) {
                // auto logout if 401 response returned from api
                this.authService.logout();
                location.reload();
            }
            
            const error = err.error.error || err.error.message || err.statusText;
            alert(error);
            return throwError(error);
        }));  
    }
}

export const errorInterceptorProvider = { provide: HTTP_INTERCEPTORS,  useClass: ErrorInterceptor,  multi: true};
  • This interceptor hooks into the response payload.
  • If the response HTTP Status Code is 401 and the user is not on the Login page, it's likely that the user isn't authenticated or a token has expired. In this case, the interceptor redirects the user back to the Login page.
  • Any other error received from the back-end REST API is properly handled and stored inside a variable named error.
  • The current interceptor simply alerts the error to the user. You can be fancier and use some Bootstrap 4 classes and styles to render a better Popup or Modal dialog to show the errors to the user.
  • Finally, the interceptor throws the errors to signal to the Angular infrastructure that the response was not okay.

The last line in the listing defines an Angular Provider for this interceptor.

Seed User Data

This article won't touch on building a registration page for the application. Instead, I'll create and seed a user record into the back-end database to test the application.

Navigate to the server folder of this application, where the back-end REST API source code exists, and run the following command:

ts-node ./node_modules/typeorm/cli.js 
-f ormconfig.json migration:create -n 
SeedUserRecord

This command uses the TypeORM CLI to create an empty migration file. Listing 7 shows the file contents.

Listing 7: TypeORM migration

import { MigrationInterface, QueryRunner } from 'typeorm';

export class SeedUserRecord1565812987671 implements MigrationInterface {         
    async up(queryRunner: QueryRunner): Promise<any> {}
    public async down(queryRunner: QueryRunner): Promise<any> {}
}

The up() function() runs when the migration is running. The `down()`` function runs when rolling back a migration.

Let's replace the content in this file with the one in Listing 8.

Listing 8: Seed User migration

import { MigrationInterface, QueryRunner } from 'typeorm';
import { UserEntity } from '../users/entity/user.entity';

export class SeedUserRecord1565812987671 implements MigrationInterface {  
    public async up(queryRunner: QueryRunner): Promise<any> {
        const userRepo = queryRunner.manager.getRepository(UserEntity);
        const user = userRepo.create({ 
            username: 'bhaidar', 
            password: '@dF%^hGb03W~',
            email: '<a href="mailto://bhaidar@gmail.com">bhaidar@gmail.com</a>',    
        });
        
        await userRepo.save(user);  
    }
    
    // tslint:disable-next-line: no-empty  public async down(queryRunner: QueryRunner): Promise<any> {}
}

I've implemented the up() function as follows:

  • The code uses the queryRunner.manager object to get access to a Repository instance for the UserEntity.
  • It then creates a new User entity record.
  • Finally, it saves the new entity into the database.

To run the migration and seed your database with a new user record, run the following command:

npm-run-all -s -l clean build:server &amp;&amp; ts-node 
./node_modules/typeorm/cli.js -f ormconfig.json 
migration:run

The command cleans any previous builds of the back-end REST API source code, it then builds and transpiles the TypeScript code to proper JavaScript code and finally runs the migration.

It's very important to use the queryRunner.manager object as you guarantee that this migration will use the same Transaction instance that runs all the migrations rather than creating a new sub-transaction.

If this confuses you, refer to part two of this series and read about NestJS and databases.

Build Application Layout

Let's quickly build the layout of the application and set up some routes so that you can start testing things like the Login page. Navigate back to the Angular application.

Master Component

Generate a new Angular Master component by running this command:

ng generate component shared/master --skipTests 
--inlineTemplate --inlineStyle

The command scaffolds a new MasterComponent class and places the HTML template together with the TypeScript code and CSS styling in a single file. Replace the content of this component with the code in Listing 9.

Listing 9: Master page

import { Component, OnInit } from '@angular/core';
import { AuthService } from 'projects/auth/src/public-api';
import { Router } from '@angular/router';

@Component({
    selector: 'app-master',
    template: `<div class="navbar navbar-expand-lg navbar-light bg-light shadow fixed-top">
                 <div class="container">
                   <a class="navbar-brand" href="#">
                     <i class="fas fa-tasks"></i>&amp;nbsp;Todozz
                   </a>
                   <button class="navbar-toggler" type="button" 
                           data-toggle="collapse" data-target="#navbarResponsive"
                           aria-controls="navbarResponsive" aria-expanded="false"
                           aria-label="Toggle navigation">
                     <span class="navbar-toggler-icon"></span>
                   </button>
                   <div class="collapse navbar-collapse" id="navbarResponsive">
                     <ul class="navbar-nav ml-auto">
                       <li class="nav-item" routerLinkActive="active">
                         <a class="nav-link" [routerLink]="['/']">Home</a>
                       </li>
                       <li class="nav-item" routerLinkActive="active">
                         <a class="nav-link" [routerLink]="['/todo']">Todo</a>
                       </li>
                       <li *ngIf="loggedIn" class="nav-item" routerLinkActive="active">
                         <a class="nav-link" (click)="logout()" href="">Logout</a>
                       </li>
                     </ul>
                   </div>
                 </div>
               </div>
               <section class="py-5 mt-5">
                 <div class="container">
                   <router-outlet></router-outlet>
                 </div>
               </section>`})

export class MasterComponent implements OnInit {  
    public loggedIn = false;

    constructor(private readonly authService: AuthService, private readonly router: Router  ) {}

    ngOnInit() {
        this.loggedIn = !!this.authService.currentUserValue;  
    }
    
    public logout(): void {
        this.authService.logout();
        this.router.navigate(['/login']);  
    }
}

The component defines a Bootstrap navigation bar that displays the brand name of the application and links to the Home page, Todo page, and a button to log out from the application. Depending on whether the user is logged-in or not, the log out button hides or shows. This logic is handled inside the code of this component.

Home Component

Let's also add a Home component to act as a landing page for the application. Generate the new component by running the following command:

ng generate component shared/home --skipTests –
inlineTemplate --inlineStyle

The command scaffolds a new HomeComponent class and places the HTML template together with the TypeScript code and CSS styling in a single file. Replace the content of this component with the code in Listing 10. The component is fairly simple and displays a message to the user.

Listing 10: Home page

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

@Component({  
    selector: 'app-home',  
    template: `<div class="row text-center">
                 <div class="col-md-12">
                   <h2 class="">Welcome to Todozz App!</h2>
                   <p>Here you can manage your Todo Lists in a breeze!</p>
                 </div>
               </div>`})
               
export class HomeComponent implements OnInit {  
    constructor() {}
    ngOnInit() {}
}

Add Routing

Now let's configure the application routing. Navigate to the /src/app/app-routing.module.ts file and replace the content of that file with the code in Listing 11.

Listing 11: Routes

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from 'projects/auth/src/public-api';
import { MasterComponent } from './shared/master/master.component';
import { HomeComponent } from './shared/home/home.component';

const routes: Routes = [
                         {
                           path: '',
                           component: MasterComponent,
                           children: [
                                       {
                                         path: '',
                                         component: HomeComponent
                                       },
                                     ]
                         },
                         {
                           path: '',
                           children: [
                                       {
                                         path: 'login',
                                         component: LoginComponent
                                       }    
                                     ]  
                         },  
                         { 
                           path: '**', 
                           redirectTo: '' 
                         }
                       ];
                    
@NgModule({  
    imports: [RouterModule.forRoot(routes)],  
    exports: [RouterModule]
})

export class AppRoutingModule {}

This routing module defines three main routes:

  • A route to the Home component
  • Another route to the Login page
  • A catch-all route that redirects the user to the Home component

Test App

Let's give it shot and run the following command:

ng serve --open

This command builds the Angular app and opens a browser instance to start browsing the app, as you see in Figure 4.

Figure 4: Home page
Figure 4: Home page

This is the application layout so far.

Test Login

Let's make use of the Auth Guard to test out the Login page. Replace the content of the /src/app/app-routing.module.ts file with the content in Listing 12.

Listing 12: Routes with Auth guard

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from 'projects/auth/src/public-api';
import { MasterComponent } from './shared/master/master.component';
import { HomeComponent } from './shared/home/home.component';

const routes: Routes = [  
                         {
                           path: '',
                           component: MasterComponent,
                           canActivate: [AuthGuard],
                           children: [
                                       {
                                         path: '',
                                         component: HomeComponent
                                       },
                                     ]
                         },  
                         {
                           path: '',
                           children: [
                                       {
                                         path: 'login',
                                         component: LoginComponent
                                       }
                                     ]  
                         },  
                         { 
                           path: '**', 
                           redirectTo: '' 
                         }];
                         
@NgModule({  
    imports: [RouterModule.forRoot(routes)],  
    exports: [RouterModule]
})

export class AppRoutingModule {}

To test the Login page, you need to run the back-end NestJS app. Navigate to the server folder where the NestJS app is hosted and run the following commands in two different terminal command windows:

npm run run:services

This command starts up the Docker container that is hosting the PostgreSQL database for this application.

npm run start:dev

This command starts the NestJS application in development mode. Back to the front-end application, at the root folder, create a new proxy.conf.json file. Replace its content with the content in Listing 13.

Listing 13: Proxy config file

{  
  "/api": {
    "target": "http://localhost:4000",
    "secure": false  
  },
  "/auth": {
    "target": "http://localhost:4000/auth/",
    "secure": false,
    "pathRewrite": { 
      "^/auth": ""
    }  
  }
}

Angular uses the proxy configuration settings to redirect requests to the back-end and to the correct port where the NestJS app is running.

Now run the application once again by issuing the following command:

ng serve --proxy-config proxy.conf.json --open

The command runs the app by using the proxy configuration file. The application detects that you're not authenticated and logged in so it redirects you to the Login page like that in Figure 5.

Figure 5: Login page
Figure 5: Login page

You enter your details and hit the Submit button. If the back-end REST API can successfully verify you, the application redirects you to the Home page once again.

Let's go back to the back-end REST API source code and remember how you've implemented the login logic there. When you enter your details on the Login page, the application sends a POST /auth/login request to the back-end REST API. It also appends the username and password data into the payload of the request.

On the back-end REST API level, NestJS chooses the corresponding Controller and Action to handle this request. In this case, the login action that you've defined on the AuthController class. It's responsible to receive all back-end requests to either log in or register a new user. You may refer back to part three of this series to learn more about user authentication.

@Post('login')  

public async login(@Body() loginUserDto: LoginUserDto): Promise<LoginStatus> {    
    return await this.authService.login(loginUserDto);  
}

The login action expects an instance of LoginUserDto containing the username and password. The action then delegates its task to the login function that the AuthService defined in Listing 14.

Listing 14: Authlogin() action

async login(loginUserDto: LoginUserDto): 

Promise<LoginStatus> {
    // find user in db    
    const user = await this.usersService.findByLogin(loginUserDto);
    
    // generate and sign token    
    const token = this._createToken(user);
    
    return {
        username: user.username,
        ...token,    
    };  
}

This function starts by:

  • Querying the database for a user given its username
  • Creating a new signed JWT when the user exists
  • Returning an object containing the username and the signed token

Then, it's the role of the AuthController.login action to return the response back to the front-end app.

As you know, the JwtInterceptor intercepts the response, extracts the user details including the JWT, and stores the data inside LocalStorage. The next time the user initiates a new request to the server, the front-end application retrieves the JWT and attaches it to the existing request.

Let's add more features and build the Todo module next.

Add Todo Library

Let's scaffold a new library to host the source code for the Todo module. Run the following command to generate the new library:

ng generate library todo 

This new library introduces a new module hosted inside todo.module.ts.

Todo Home Component

Let's add the landing component of this library by running the command:

ng generate component todo-home --project=todo –
skipTests --inlineStyle --inlineTemplate 

This command generates the TodoHomeComponent. Replace the content of this component with the content in Listing 15.

Listing 15: Todo Home component

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

@Component({  
    selector: 'lib-todo-home',  
    template: `<div class="row my-2">
                 <div class="col-md-6">
                   <div class="row">
                     <div class="col-md-12 bg-light py-3 text-center">
                       <h5>Todo Lists</h5>
                     </div>
                   </div>
                   <div class="row">
                     <div class="col-md-12 my-2"></div>
                   </div>
                 </div>
                 <div class="col-md-6">
                   <div class="row">
                     <div class="col-md-12 bg-light py-3 border-left border-3 border-primary text-center"> 
                       <h5>Tasks</h5>
                     </div>
                   </div>
                   <div class="row">
                     <div class="col-md-12 my-2">
                       <router-outlet></router-outlet>
                     </div>
                   </div>
                 </div>
               </div>`,
    styles: [ `.border-3 { border-width: 3px !important; }`  ]
})

export class TodoHomeComponent implements OnInit {  
    constructor() {}
    ngOnInit() {}
}

Navigate to the Todo link on the home page and see the results in Figure 6.

Figure 6: Todo Home page
Figure 6: Todo Home page

The component splits the screen into two sections. The first hosts the Todo lists and the second hosts the Tasks under each Todo list.

Todo Create Component

Generate a new Todo Create component by running the following command:

ng generate component components/todo-create –
project=todo --skipTests --inlineStyle –
inlineTemplate 

The command above creates a new TodoCreateComponent inside the components folder in the todo library. Replace the content of this file with the content in Listing 16.

Listing 16: Todo Create component

import { Component, OnInit, EventEmitter, Output } from '@angular/core';
import { DoAction } from 'projects/app-common/src/public-api';

@Component({
    selector: 'lib-todo-create',
    template: `<div class="row my-2 mb-4">
                 <div class="col-md-8 offset-md-2">
                   <input [(ngModel)]="todo" (keyup.enter)="OnEnter()" class="form-control" placeholder="Type a Todo and hit (Enter)" />     
                 </div>
               </div>`
})

export class TodoCreateComponent implements OnInit {  
    public todo = '';
    @Output()  public action: EventEmitter<DoAction> = new EventEmitter();
    constructor() {}
    ngOnInit() {}
    public OnEnter() {
        this.action.emit({
            type: 'add-todo', 
            spayload: this.todo 
        });
        this.todo = '';  
    }
}

The component is fairly simple. It defines a Textbox allowing the user to enter a new Todo List name and hit Enter to save the Todo List in the database.

The component listens to the Enter key on the Textbox and then emits an Output action containing the type of add-todo and a payload of the name of the Todo List itself. It emits this action to the outside world, that a new Todo List name has been entered. You will see shortly how to make use of this Output action to store the new Todo List in the database.

Todo List Component

Generate a new Todo List component by running the following command:

ng generate component components/todo-list –
project=todo --skipTests --inlineStyle –
inlineTemplate 

The command creates a new TodoListComponent inside the components folder in the todo library. Replace the content of this file with the content in Listing 17.

Listing 17: Todo List component

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Todo } from '../../models/todo.model';
import { DoAction } from 'projects/app-common/src/public-api';

@Component({
    selector: 'lib-todo-list',  
    template: `<div *ngIf="!todos?.length; else show">No todos yet!</div>
               <ng-template #show>
                 <div class="list-group">
                   <div *ngFor="let todo of todos; let i = index; trackBy: trackByFn" class="todos">
                     <div class="action">
                       <button (click)="doAction(todo)" class="btn btn-danger btn-lg" title="Delete {{ todo?.name }}">                       
                         <i class="fa fa-trash"></i>
                       </button>
                     </div>
                     <div class="todo">
                       <a href="#" [routerLink]="['tasks', todo.id]" routerLinkActive="list-group-item-primary" class="list-group-item list-group-item-action">
                         ({{ i + 1 }}) {{ todo?.name }}
                       </a>
                     </div>
                   </div>
                 </div>
               </ng-template>`,  
    styles: [`.todos { display: flex; justify-content: center; }
              .todos .todo { flex-grow: 1; flex-shrink: 0; max-width: 90%; }
              .todos .action { margin-right: 5px; }`]
})

export class TodoListComponent implements OnInit { 
    @Input() public todos: Todo[];
    @Output()  public action: EventEmitter<DoAction> = new EventEmitter();
    constructor() {}
    ngOnInit() {}
    public trackByFn(index: number, item: Todo) {
        return index;  
    }
    public doAction(todo: Todo): void {
        this.action.emit({
            type: 'delete-todo', 
            payload: todo });
    }
}

The component receives as input an array of all Todo List items to display to the user. It uses Bootstrap 4 list-group styles to display all the items.

The component emits an Output action containing the type of delete-todo and a payload of the Todo List item itself. It emits this action to the outside world once the user clicks to delete a single Todo List item. It passes over the corresponding list item the user is trying to delete.

Finally, the component places a router-outlet component under the Tasks section. When the user clicks on a single Todo List item, the application renders the Tasks page for this Todo List item inside the router-outlet allowing the user to view both the TodoList Items and Tasks under the Todo List item side by side.

Todo Component

Now let's generate the TodoComponent that will host both the TodoCreateComponent and TodoListComponent.

ng generate component components/todo –
project=todo --skipTests --inlineStyle –
inlineTemplate 

The command above creates a new TodoComponent inside the components folder in the todo library. Replace the content of this file with the content in Listing 18.

Listing 18: Todo component

import { Component, OnInit } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { DoAction } from 'projects/app-common/src/public-api';
import { Todo } from '../models/todo.model';
import { TodoService } from '../services/todo.service';
import { Router } from '@angular/router';

@Component({  
    selector: 'lib-todo', 
    template: `<lib-todo-create (action)="doAction($event)"></lib-todo-create>
               <lib-todo-list [todos]="todos$ | async" (action)="doAction($event)"></lib-todo-list>`
})

export class TodoComponent implements OnInit {  
    public todos$: Observable<Todo[]>;  
    private refresh$ = new BehaviorSubject<any>('');
    
    constructor(private readonly router: Router,private readonly todoService: TodoService  ) {}
    
    ngOnInit() {
        this.todos$ = this.refresh$.pipe(switchMap(() => this.todoService.findAll()));  
    }
    
    public doAction({ type, payload }: DoAction): void {
        switch (type) {
            case 'add-todo':
                this.createTodo(payload);
                break;
            case 'delete-todo':
                this.deleteTodo(payload);
                break;
            default:
                console.log('Unknown action type');    
        }  
    }
    
    private createTodo(todo: string): void {
        this.todoService.create({ name: todo })
                        .subscribe(() => this.refresh$.next(''));  
    }
    
    private deleteTodo(todo: Todo): void {
        if (confirm('Are you sure you want to delete this item?')) {
            this.todoService.delete(todo.id).subscribe(() => {
                this.refresh$.next('');
                this.router.navigate(['/todo']);
            });    
        }  
    }
}

This component listens to the Output actions of both components as follows:

<lib-todo-create (action)="doAction($event)"></lib-todo-create>
<lib-todo-list [todos]="todos$ | async" (action)="doAction($event)"></lib-todo-list>

The TodoComponent defines the todos$ observable that passes it over to the TodoListComponent by means of an Async Pipe. Whenever this observable changes, the TodoListComponent receives a new fresh array of Todo Items to display and render to the user.

As a recap, the TodoCreateComponent emits the add-todo action while the TodoListComponent emits the delete-todo action. The TodoComponent handles both actions inside the doAction($event) function in Listing 18.

If the action emitted is of type add-todo, the function calls another function named createTodo() together with the payload of the action. If the action emitted is of type delete-todo, the function calls another function named deleteTodo() together with the payload of the action.

The createTodo() function, in turn, calls the `TodoService.create()`` function, passing to it a Todo model object as follows:

private createTodo(todo: string): void {
    this.todoService.create({ name: todo }).subscribe(() => this.refresh$.next(''));
}

The deleteTodo() function, in turn, calls the TodoService.delete() function passing to it the ID of the Todo List object:

private deleteTodo(todo: Todo): void {
    if (confirm(`Are you sure you want to delete this item?`)) {
        this.todoService.delete(todo.id).subscribe(() => {
            this.refresh$.next('');
            this.router.navigate(['/todo']);
        });
    }
}

The code navigates the user back to the /todo page after a successful deletion of a Todo List item. This is directly related to the existence of an internal router-view component, as you will see shortly.

You define the /todo/src/lib/models/todo.model.ts as:

export interface Todo {  
    id?: string;  
    name: string;  
    createdOn?: Date;
}

The TodoService in Listing 19 wraps a few calls to the back-end REST API via the HttpClient**lass. For instance, the service defines the `create()`` function like so:

public create(todo: Todo): Observable<Todo> {
    return this.http.post<Todo>(this.baseUrl, todo, httpOptions).pipe(catchError(this.handleError));
}

The function sends a POST request to the back-end REST API, passing along a request payload containing the Todo List object and some options. It finally handles any errors generated out of this call to a dedicated function named handleError() defined in the service in Listing 19.

Listing 19: Todo service

import { Injectable } from '@angular/core';
import { HttpHeaders,  HttpClient,  HttpErrorResponse} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { Todo } from '../models/todo.model';
import { catchError, map } from 'rxjs/operators';

const httpOptions = {
    headers: new HttpHeaders({
        'Content-Type': 'application/json'  
    })
};

@Injectable({providedIn: 'root'})

export class TodoService {
    private baseUrl = 'api/todos'; // URL to web api
    constructor(private readonly http: HttpClient) {}
    
    public create(todo: Todo): Observable<Todo> {
        return this.http.post<Todo>(this.baseUrl, todo, httpOptions).pipe(catchError(this.handleError));  
    }
    
    public findAll(): Observable<Todo[]> {
        return this.http.get<Todo[]>(this.baseUrl, httpOptions).pipe(map((results: any) => results.todos), catchError(this.handleError));  
    }
    
    public delete(id: string): Observable<{}> {
        const url = `${this.baseUrl}/${id}`;
        
        // DELETE api/todos/42-5c-...    
        return this.http.delete(url, httpOptions).pipe(catchError(this.handleError));  
    }
    
    private handleError(error: HttpErrorResponse) {
        if (error.error instanceof ErrorEvent) {
            console.error('An error occured:', error.error.message);
        } else {
            console.log(`Backend returned code ${error.status}, body was: ${error.status}`);    
        }
    return  throwError(`Something bad happened; please try again later.`);  
    }
}

The service defines the httpOptions variable as follows:

const httpOptions = {
    headers: new HttpHeaders({
        'Content-Type': 'application/json'
    })
};

The remaining components related to Task management have similar implementation to these components. I'll leave it to you to go and check the source code accompanying this article to see their implementations.

Test Todo

Back to Listing 15 (see above), find the following section inside the TodoHomeComponent:

<div class="row">
    <div class="col-md-12 my-2"></div>
</div>

Replace it with the following:

<div class="row">
  <div class="col-md-12 my-">
    <lib-todo></lib-todo>
  </div>
</div>

Now the TodoHomeComponent hosts the TodoComponent inside it. Let's add the corresponding routes to the application so that you can navigate to the Todo components. Open the /src/app/app-routing.module.ts file and replace its content as in Listing 20.

Listing 20: Final routes

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from 'projects/auth/src/public-api';
import { MasterComponent } from './shared/master/master.component';
import { HomeComponent } from './shared/home/home.component';
import { AuthGuard } from 'projects/auth/src/lib/auth.guard';
import { TodoHomeComponent, TaskComponent } from 'projects/todo/src/public-api';

const routes: Routes = [
                         {
                           path: '',
                           component: MasterComponent,
                           canActivate: [AuthGuard],
                           children: [
                                       {
                                         path: '',
                                         component: HomeComponent
                                       },
                                       {
                                         path: 'todo',
                                         component: TodoHomeComponent,
                                         children: [
                                                     {
                                                       path: 'tasks/:id',
                                                       component: TaskComponent
                                                     }
                                                   ]
                                       }
                                     ]
                         },  
                         {
                           path: '',
                           children: [
                                       {
                                         path: 'login',
                                         component: LoginComponent
                                       }
                                     ]
                         },
                         { 
                           path: '**', 
                           redirectTo: '' 
                         }];
                         
@NgModule({
    imports: [RouterModule.forRoot(routes)],  
    exports: [RouterModule]
})

export class AppRoutingModule {}

Run the Docker container, back-end REST API, and Angular app and check the results in Figure 7:

Figure 7: Todo Component
Figure 7: Todo Component

You can see both TodoCreateComponent and TodoListComponent rendered on top of each other. Start playing around by adding new Todo List items or deleting existing ones. Assuming that I've implemented the rest of the Task components, if you click on any of the Todo List items, you should see the Tasks management screen as in Figure 8:

Figure 8: Todo Home with Tasks page
Figure 8: Todo Home with Tasks page

You've got a new Tasks screen that is similar to that of the Todo Lists to add new tasks or delete existing ones.

When you enter a new Todo List item and hit the Enter key, the application sends a POST /api/todos request together with a request payload that contains the Todo List item. The back-end REST API receives the request and handles it via the TodoController.create action. Listing 21 shows the body of the TodoController.create action.

Listing 21: TodoController create() action

@Post()  
@UseGuards(AuthGuard())

async create(@Body() createTodoDto: CreateTodoDto, @Req() req: any,): Promise<TodoDto> {
    const user = req.user as UserDto;
    return await this.todoService.createTodo(user, createTodoDto);  
}

You have decorated the create() action with the @UserGuards(AuthGuard()) decorator. This decorator ensures that the AuthGuard runs before the code executes this action.

The AuthGuard, if you've been through part three of this series, shows you that it will look for a JWT inside the request Authorization header and verify it. Upon a successful verification of the user, the create() action executes and returns a response to the front-end app.

You can find the source code of this article and the rest of this series in this repo: https://github.com/bhaidar/nestjs-todo-app.

Conclusion

This is the final part in this series on learning NestJS Step-by-Step. I've introduced a front-end application written in Angular to connect to the back-end REST API. You can see how easy it is to connect the two. There's nothing different from any other front-end application or other technology, like ASP.NET Core or PHP.

NestJS is a large framework and covers so much more than I can fit into a four-part series. I recommend that you always check the rich documentation website they offer, as it is always up to date and includes further details that could be useful while developing and using the NestJS framework.