Build a full-stack web application with Angular 7 and Nest.js

Build a full-stack web application with Angular 7 and Nest.js

Throughout this tutorial we'll be building a full-stack web application with Nest.js and Angular 7. We'll also introduce both frameworks and learn how to get started using each one of them.

The project we'll be building a simple contacts management application for managing a set of contacts in an Angular and Material Design interface retrieved from a Nest.js RESTful backend.

Prerequisites

Let's get started with listing the prerequisites you need to have for this tutorial. Basicaly, you will need to have:

  • Both Node.js and NPM installed on your development machine. You can simply go to the official website and download the binaries designed for your operating system or you can check the official instructions for how to install the latest versions in your system. You can also check NVM (Node Version Manager) which is the recommended way for installing one or multiple versions of Node in your system without conflicts.
  • Familiarity with TypeScript. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

If you have these prerequisites, you are good to go!

Tip
Learn how to create a CI/CD pipeline in Buddy, that will build, test and deploy your Angular application on a single push to a branch.

Introducing Angular

Angular is a frontend web framework for building mobile and desktop applications. It's built with TypeScript by Google and it's one of the most popular frameworks among frontend developers in the world. It allows developers to build applications for web, mobile web, native mobile and native desktop. It's makes use of libraries like RxJS and Immutable.js for efficiently working with huge data requirements.

Angular focuses on performance, speed and scalability and provides powerful tooling and features.

Angular follows a component-based architecture where an application is a tree of encapsulated and loosely coupled components with defined inputs and outputs. Code common between different components is encapsulated in services which are simply global singletons that get injected where they are needed via dependency injection.

Installing Angular CLI

The Angular CLI allows developers to easily generate Angular projects with the best practices. It also helps in generating various artifacts like components and services from the command line. You can also set up routing and Angular Material in your application using the ng add command.

Open a new terminal and run the following command to install the Angular CLI from npm:

bash
$ npm install -g @angular/cli $$

Note: Please note that you may need to add sudo before your command in macOS and Linux or use a CMD prompt with administrator rights to install npm packages globally. If the commands fails, you can also fix your npm permissions.

If the installation is successful, you can invoke the ng command to run various CLI commands from your terminal. For example let's display the installed version of the CLI:

bash
$ ng version $$

This is a screenshot of the output:

Image loading...Angular CLI version

What is Nest.js

Nest is an open source MIT-licensed Node.js framework inspired by Angular for easily building efficient and scalable server-side applications. It's built with TypeScript and allows you to use the concepts of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

Nest doesn't reinvent the wheel but makes use of the existing mature tools such as Express and TypeORM. On top of these tools, it provides an architecture that helps developers effortlessly build easily testable, scalable, loosely coupled, and maintainable web applications.

Installing Nest CLI

Nest CLI is a powerful command line interface that allows developers to generate a Nest.js project and the different artifacts like controllers and services. It also provides out of the box scripts for locally serving your project and building the final production-ready application that can be deployed on the web.

Nest CLI is based on the @angular-devkit package and provides its own schematics for Nest.js development.

Let's gets started with installing the Nest CLI. Open a new terminal and run the following command to install the CLI from npm:

bash
$ npm install -g @nestjs/cli $$

After finishing with the installation process, you will be able to use various CLI commands from your terminal. Let's see a first command:

bash
$ nest --help $$

This will display a list of the available commands at your disposal.

Creating our Angular and Nest Projects

After installing the required utilities, let's now proceed to create our project. Head back to your terminal and navigate to your working directory then create a folder for both the frontend and backend projects:

bash
$ cd ~/demos $ mkdir nest-angular-app $$$

Next, navigate to your created project and generate the backend and frontend projects using the following commands:

bash
$ cd nest-angular-app $ ng new frontend $ nest new backend $$$$

The Angular CLI will prompt you if you Would you like to add Angular routing? (y/N) Enter y( This will automatically set up routing in your project) and Which stylesheet format would you like to use? (Use arrow keys) Choose CSS and hit Enter.

The Nest CLI will ask you for a bunch of information about your project such as the name, description and author. Enter the required information or leave empty for the default values. The CLI will create the necessary files and then prompt you for which package manager to use to install the dependencies. Choose npm.

Next, navigate to your frontend project and serve it using a live-reload development server using the following commands:

bash
$ cd frontend $ ng serve $$$

Your Angular application will be available from the 127.0.0.1:4200 address. If you navigate to that address using your browser you should be able to see the following page:

Image loading...Angular app

Open a new terminal and navigate to the backend project then run the development server using the following commands:

bash
$ cd backend $ npm run start:dev $$$

Your Nest application will be available from the 127.0.0.1:3000 address. If you go to that address with your web browser, you will get a blank page with a Hello World!:

Image loading...Nest App

Nest CLI makes use of nodemon which is an utility that will watch your source code for any changes and automatically reload your development server.

Now that we have both the frontend and backend apps up and running, let's start developing our full-stack contact management application.

Creating a MySQL Database and Setting up TypeORM

Open a new terminal and run the following command to invoke the mysql client:

bash
$ mysql -u root -p $$

You will be prompted for a password. Enter the password you configured for your MySQL during the installation process.

Next, create a MySQL database:

bash
mysql> create database nestngdb; $$

Nest.js makes use of TypeORM which is the most mature Object Relational Mapper (ORM) available in TypeScript. It's available from the @nestjs/typeorm package.

Navigate to your backend project and run the following commands to install the required dependencies to work with TypeORM and MySQL in your Nest.js project:

bash
$ npm install --save @nestjs/typeorm typeorm mysql $$

Next, you need to open the src/app.module.ts file and import the TypeOrmModule inApplicationModule:

ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', database: 'nestngdb', username: 'root', password: 'YOUR_DATABASE_PASSWORD', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, }) ], controllers: [AppController], providers: [AppService], }) export class AppModule { }

We use the forRoot() method of TypeOrmModule which takes a configuration object similar to the one you would need to pass to the createConnection() method of TypeORM.

Note: Make sure to replace YOUR_DATABASE_PASSWORD with your own MySQL database password.

In the configuration object we specify the following options:

  • The mysql database as the type which tells TypeORM to connect to a MySQL database,
  • The nestngdb database we previously created,
  • The username and password for connecting to our MySQL server,
  • The entities files (that end with .entity.ts or .entity.jsextensions) which contain our ORM entities that will be mapped to SQL tables by TypeORM.
  • The synchronize option (which takes true or false). If true, It tells TypeORM to automatically sync the database tables with the entities each time you run the app. This is helpful in development but not recommended in production.

Now that you have configured TypeORM in your project, you'll be able to inject the Connection and EntityManager services in your Nest.js services and controllers to work with the database.

Creating a Contact Entity

Next, let's create a Contact entity that will be mapped to a contact table in the MySQL database that will hold information about contacts in our application.

Create a src/entities/contact.entity.ts file and add the following code:

ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Contact { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() title: string; @Column() email: string; @Column() phone: string; @Column() address: string; @Column() city: string; }

Next, you will need to import the Contact entity and include it in the imports array of the root module using the forFeature() method. Open the src/app.module.ts file and add the following changes:

ts
// ... import { Contact } from 'entities/contact.entity'; @Module({ imports: [ /* ... */ TypeOrmModule.forFeature([Contact]), ], controllers: [AppController], providers: [AppService], }) export class AppModule { }

Once you save your file, the database table that corresponds to the Contact entity will be created in your database.

Note: You can head to your mysql client and execute the show tables; instruction to make sure a contact table is created.

Creating ContactService

Next, let's create a Nest service that contains the code for working with the contact table. This service will basically contain the CRUD operations necessary for creating, reading, updating and deleting contacts from the database. Head back to your terminal and run the following command to generate a service:

bash
$ nest generate service contact $$

Next, open the src/contact.service.ts file and add the following imports:

ts
import { Repository } from 'typeorm'; import { Repository, UpdateResult, DeleteResult } from 'typeorm'; import { Contact } from 'entities/contact.entity';

Next, inject the Contact repository via the constructor:

ts
@Injectable() export class ContactService { constructor( @InjectRepository(Contact) private contactRepository: Repository<Contact> ) { } }

Next, add the CRUD methods:

ts
async create(contact: Contact): Promise<Contact> { return await this.contactRepository.save(contact); } async readAll(): Promise<Contact[]> { return await this.contactRepository.find(); } async update(contact: Contact): Promise<UpdateResult> { return await this.contactRepository.update(contact.id,contact); } async delete(id): Promise<DeleteResult> { return await this.contactRepository.delete(id); }

Building the REST API

Let's now create the RESTful endpoints that our Angular frontend will be calling to retrieve and create data on the server. Head back to your terminal and run the following command to generate a controller:

bash
$ nest generate controller contacts $$

Next, open the src/contacts/contacts.controller.ts file and import then inject ContactService

ts
import { Controller } from '@nestjs/common'; import { ContactService } from 'contact.service'; @Controller('contacts') export class ContactsController { constructor(private contactService: ContactService){ } }

You need to import the Get, Post,Put, Delete, Body and Param symbols and also the Contact entity:

ts
import { Controller, Get, Post,Put, Delete, Body, Param } from '@nestjs/common'; import { Contact } from 'entities/contact.entity';

Finally, add the REST endpoints:

ts
@Get() read(): Promise<Contact[]> { return this.contactService.readAll(); } @Post('create') async create(@Body() contact: Contact): Promise<any> { return this.contactService.create(contact); } @Put(':id/update') async update(@Param('id') id, @Body() contact: Contact): Promise<any> { contact.id = Number(id); return this.contactService.update(contact); } @Delete(':id/delete') async delete(@Param('id') id): Promise<any> { return this.contactService.delete(id); }

That's it! Our REST API is ready. At this point, we can use a REST API client like cURL or Postman to communicate with the API.

You can send a POST request to the /contacts/create endpoint to create a contact:

Image loading...Nest.js API

You can also send a GET request to /contacts endpoint to retrieve all created contacts:

Image loading...Nest.js API

Enabling CORS

Since we'll be communicating with our REST API from an Angular frontend running on another domain we need to enable CORS. Open the src/main.ts file and call the enableCors() method:

ts
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); await app.listen(3000); } bootstrap();

Implementing the Angular Interface

We have previously generated the Angular frontend and started the development server. Now, let's create the UI of our application.

Head back to a new terminal and run the following command to set up Angular Material:

bash
$ ng add @angular/material $$

When prompted to Choose a prebuilt theme name, or "custom" for a custom theme Choose Purple/Green:

Image loading...Angular Material

Next, you will be prompted if you want to Set up HammerJS for gesture recognition? (Y/n) and Set up browser animations for Angular Material? (Y/n) You can choose the default answer which is Y.

That's it! Angular Material will be automatically installed and configured in your project.

After that, you need to import the Angular Material components that you want to use in your project. Open the src/app/app.module.ts file:

ts
// [...] import { MatInputModule, MatButtonModule, MatCardModule, MatFormFieldModule,MatTableModule } from '@angular/material'; @NgModule({ declarations: [ AppComponent, ContactComponent ], imports: [ // [...] MatTableModule, MatCardModule, MatInputModule, MatFormFieldModule, MatButtonModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }

We'll be using the table, card, input, form field and button components. You can find the full list of components that you can use from the official docs.

Setting up HttpClient and Forms

We'll be using HttpClient and a template-based form in our application so we need to import their modules. Open the src/app/app.module.ts file and update it accordingly:

ts
// [...] import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ // [...] HttpClientModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }

Creating a Contact Model

Next, let's create a Contact model. In your terminal, run the following command:

bash
$ ng generate class contact $$

Open the src/app/contact.ts file and update it accodringly:

ts
export class Contact { id: number; name: string; title: string; email: string; phone: string; address: string; city: string; }

Creating an Angular Service

Next, let's create a service that contains the code for interfacing with the REST API. In your terminal, run the following command:

bash
$ ng generate service api $$

Next, open the src/app/api.service.ts file and start by importing and injecting HttpClient:

ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Contact } from './contact'; @Injectable({ providedIn: 'root' }) export class ApiService { constructor(private httpClient: HttpClient) { } }

We also imported the Contact model.

Next, define the API_SERVER variable that contains the address of the API server:

ts
export class ApiService { API_SERVER = "http://localhost:3000";

Next, add the CRUD operations:

ts
public readContacts(){ return this.httpClient.get<Contact[]>(`${this.API_SERVER}/contacts`); } public createContact(contact: Contact){ return this.httpClient.post<Contact>(`${this.API_SERVER}/contacts/create`, contact); } public updateContact(contact: Contact){ return this.httpClient.put<Contact>(`${this.API_SERVER}/contacts/${contact.id}/update`, contact); } public deleteContact(id: number){ return this.httpClient.delete(`${this.API_SERVER}/contacts/${id}/delete`); }

We use the get(), post(), put() and delete() methods of HttpClient to send the GET, POST, PUT and DELETE requests to the corresponding endpoints of the API backend.

Creating the Contacts Component

Now, let's create the component where we can display our table of contacts and a form to create contacts.

In your terminal, run the following command:

bash
$ ng generate component contact $$

Next, open the src/app/app-routing.module.ts file and add a route for accessing the component:

ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { ContactComponent } from './contact/contact.component'; const routes: Routes = [ {path: "", pathMatch: "full", redirectTo: "contacts"}, {path: "contacts", component: ContactComponent} ]; // [...]

We add a /contacts route mapped to ContactComponent and a route to redirect the empty path to the /contacts path.

Next open the src/app/contact/contact.component.ts and add the following imports:

ts
import { ApiService } from '../api.service'; import { Contact } from '../contact';

Next inject ApiService and declare the following variables:

ts
export class ContactComponent implements OnInit { displayedColumns : string[] = ['id', 'name', 'title', 'email', 'phone', 'address', 'city', 'actions']; dataSource = []; contact = {}; constructor(private apiService: ApiService) { }

The displayedComumns variable holds the name of the columns that will be displayed in the Angular Material Table we'll be adding later. The dataSource variable will contain the data of the table and the contact variable will contain the selected contact from the table.

Next, retrieve the contacts from the server when the component is initialized:

ts
ngOnInit() { this.apiService.readContacts().subscribe((result)=>{ this.dataSource = result; }) }

We simply call the readContacts() method of ApiService in the ngOnInit() method of the component and we subscribe to the returned RxJS Observable then we assign the result to the dataSource variable.

Next, add the selectContact() and newContact() methods:

ts
selectContact(contact){ this.contact = contact; } newContact(){ this.contact = {}; }

The selectContact() method assigns the selected contact to the contact variable and the newContact() method assigns an empty object to the contact variable. These two methods will be bound to a select and new buttons that will be adding later in our UI.

Finally, add the other CRUD methods to create, update and delete contacts:

ts
createContact(f){ this.apiService.createContact(f.value).subscribe((result)=>{ console.log(result); }); } deleteContact(id){ this.apiService.deleteContact(id).subscribe((result)=>{ console.log(result); }); } updateContact(f){ f.value.id = this.contact['id']; this.apiService.updateContact(f.value).subscribe((result)=>{ console.log(result); }); }

These methods are simply wrappers of the corresponding methods in ApiService.

Creating the Template

After adding the code for the component, we now need to create the template. Open the src/app/contact/contact.component.html and start by adding a Material Design table that will display the contacts:

html
<mat-card> <mat-card-header> <mat-card-title>Contacts</mat-card-title> </mat-card-header> <mat-card-content> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8" style="width: 100%;"> <ng-container matColumnDef="id"> <th mat-header-cell *matHeaderCellDef>ID </th> <td mat-cell *matCellDef="let element"> {{element.id}} </td> </ng-container> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef>Name </th> <td mat-cell *matCellDef="let element"> {{element.name}} </td> </ng-container> <ng-container matColumnDef="title"> <th mat-header-cell *matHeaderCellDef> Title </th> <td mat-cell *matCellDef="let element"> {{element.title}} </td> </ng-container> <ng-container matColumnDef="email"> <th mat-header-cell *matHeaderCellDef> Email </th> <td mat-cell *matCellDef="let element"> {{element.email}} </td> </ng-container> <ng-container matColumnDef="phone"> <th mat-header-cell *matHeaderCellDef> Phone </th> <td mat-cell *matCellDef="let element"> {{element.phone}} </td> </ng-container> <ng-container matColumnDef="address"> <th mat-header-cell *matHeaderCellDef> Address </th> <td mat-cell *matCellDef="let element"> {{element.address}} </td> </ng-container> <ng-container matColumnDef="city"> <th mat-header-cell *matHeaderCellDef> City </th> <td mat-cell *matCellDef="let element"> {{element.city}} </td> </ng-container> <ng-container matColumnDef="actions"> <th mat-header-cell *matHeaderCellDef> Actions </th> <td mat-cell *matCellDef="let element"> <button mat-raised-button (click)="deleteContact(element.id)" color="secondary">Delete Contact</button> <button mat-raised-button (click)="selectContact(element)" color="secondary">Select Contact</button> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table> </mat-card-content> </mat-card>

You can find more details of how you create a Material table from the official docs.

Next, let's add a form to create and update contacts just below the table:

html
<mat-card> <mat-card-header> <mat-card-title>Create a Contact</mat-card-title> </mat-card-header> <mat-card-content> <form #f ="ngForm" class="mat-elevation-z8" style="width: 100%; padding: 5px;"> <mat-form-field> <input matInput placeholder="Name" name="name" [(ngModel)] = "contact.name" required> </mat-form-field> <mat-form-field> <input matInput placeholder="Title" name="title" [(ngModel)] = "contact.title" required> </mat-form-field> <mat-form-field> <input matInput placeholder="Email" name="email" [(ngModel)] = "contact.email" required> </mat-form-field> <mat-form-field> <input matInput placeholder="Phone" name="phone" [(ngModel)] = "contact.phone" required> </mat-form-field> <mat-form-field> <textarea placeholder="Address" name="address" [(ngModel)] = "contact.address" matInput></textarea> </mat-form-field> <mat-form-field> <input matInput placeholder="City" name="city" [(ngModel)] = "contact.city" required> </mat-form-field> </form> </mat-card-content> <mat-card-actions> <button mat-raised-button *ngIf="!contact.id" (click)="createContact(f)" color="primary">Save Contact</button> <button mat-raised-button *ngIf="contact.id" (click)="updateContact(f)" color="primary">Update Contact</button> <button mat-raised-button (click)="newContact()" color="primary">New..</button> </mat-card-actions> </mat-card>

We simply create a template-based table with the fields for entering information about the contact to create or update. We also add buttons for creating, updating, selecting contacts and emptying the form.

Next, open the src/app/app.component.html and change accordingly:

html
<div style="text-align:center"> <h1> Contact Management </h1> </div> <router-outlet></router-outlet>

We add a title and the router outlet where the router will insert the matched component.

This is a screenshot of the page after adding two contacts:

Image loading...Angular Material Interface

This is a screenshot of the page after selecting a contact:

Image loading...Angular Material Interface

Conclusion

Throughout this tutorial we've used Nest.js and Angular 7 to create a full-stack REST API web application from scratch. We have also used Angular Material to build our application UI. You can find the source code from this GitHub repository.

Read similar articles