Daisy Rees

Daisy Rees

1563155047

Building a web app using Nestjs, Fastify, MongoDB and Angular 8

Learn to build the efficient, reliable and scalable server-side web app using Nestjs, Fastify, MongoDB and Angular 8. Nest.js is a complete development kit for building scalable server-side applications. The Fastify is alternative for Express.js as HTTP-Server dan claimed faster than Express.js. Angular 8 is the latest, smaller build and faster Angular version. So, this is the perfect combination for building better web apps.

As usual, we will learn to build the efficient, reliable and scalable server-side web app through the step by step from the scratch. Starting to build the backend application using Nestjs and Fastify then complete with Angular 8 as frontend application. The Angular 8 application access the data layer via RESTful API that provided by Nestjs which served by prefix URL /api. Nest.js accessing the MongoDB via Mongoose.js. All required libraries and modules that use by the backend already prepared very well by The Nestjs.

The following libraries, frameworks, and modules are required for this tutorial:

Before move further, make sure we have installed the Node.js and MongoDB server. To check if they were installed, type these commands in the terminal or command line.

node -v
v10.15.1
npm -v
6.9.0
mongo --version
MongoDB shell version: 3.2.11

Install Nestjs and Create a New Application

We will start the steps by building a backend or server-side application using Nestjs. For that, we have to install the Nest.js first by typing this command in the terminal or command line.

sudo npm i -g @nestjs/cli

You can run that command without sudo in the command line. Next, to create a new server-side application, type this command.

nest new nest-angular8

Next, go to the newly created project folder then run the application for the first time by type this command.

npm start

Open the browser then go to [http://localhost:3000](http://localhost:3000) and you should see the text Hello world!` in the web browser. Next, install the Fastify.js to increase the performance of Nestjs application because by default Nestjs using Express.js. Type this command to install it.

npm i --save @nestjs/platform-fastify

Next, open and edit src/main.ts then modify/add these imports.

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

Change the bootstrap function to these lines of codes.

async function bootstrap() {
  const app = await NestFactory.create(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000);
}
bootstrap();

Now, the Fastify.js will run as the default engine for Nestjs application.

Install and Configure Mongoose.js

We will use Mongoose.js as ORM for MongoDB. For that, type this command to install the Nest.js Mongoose.js modules.

npm install --save @nestjs/mongoose mongoose

Next, create a database folder inside the src folder. We will create all folder and files manually, if you like, you can generate them using Nestjs CLI.

mkdir database

Add 2 files to the database folder.

touch src/database/database.module.ts
touch src/database/database.provider.ts

Next, open and edit src/database/database.provider.ts then add these lines of codes.

import * as mongoose from 'mongoose';

export const databaseProviders = [
    {
        provide: 'DATABASE_CONNECTION',
        useFactory: async (): Promise =>
        await mongoose.connect('mongodb://localhost/angular8-crud', { useNewUrlParser: true, useFindAndModify: false }),
    },
];

Provider in this database module is used for connecting the Nest application with MongoDB database. Next, open and edit src/database/database.module.ts then add these lines of codes.

import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
    providers: [...databaseProviders],
    exports: [...databaseProviders],
})
export class DatabaseModule {}

Create a Mongoose Schema

The next steps to create a CRUD RESTful API is to create a Mongoose schema or model. We will make a modular application for each object, so, for the Article object, we will create a folder for it first.

mkdir src/article

Next, create a schema folder and a schema file inside that folder.

mkdir src/article/schemas
touch src/article/schemas/article.schemas.ts

Next, open and edit that file then add these lines of Typescript codes.

import * as mongoose from 'mongoose';

export const ArticleSchema = new mongoose.Schema({
    title: { type: String, required: true },
    author: { type: String, required: true },
    description: { type: String, required: true },
    content: { type: String, required: true },
    updatedAt: { type: Date, default: Date.now },
});

Next, create a provider file to register the Mongoose schema as a model.

touch src/article/article.providers.ts

Open and edit that file then add these lines of Typescript codes.

import { Connection } from 'mongoose';
import { ArticleSchema } from './schemas/article.schemas';

export const articleProviders = [
    {
        provide: 'ARTICLE_MODEL',
        useFactory: (connection: Connection) => connection.model('Article', ArticleSchema),
        inject: ['DATABASE_CONNECTION'],
    },
];

Create the Routes for Article

Creating RESTful API using Nestjs similar to ASP Net Core Web API, especially when interacting with the Database. We have to create DTO and Interface to access Mongoose schema. Next, create a folder and a file for DTO inside article folder.

mkdir src/article/dto
touch src/article/dto/article.dto.ts

Open and edit that file then add these lines of Typescript codes.

export class ArticleDto {
    readonly title: string;
    readonly author: string;
    readonly description: string;
    readonly content: string;
}

Next, create a folder and file for the interface inside the article folder.

mkdir src/article/interfaces
touch src/article/interfaces/article.interface.ts

Open and edit that file then add these lines of Typescript codes.

import { Document } from 'mongoose';

export interface Article extends Document {
    readonly title: string;
    readonly author: string;
    readonly description: string;
    readonly content: string;
}

That interface similar with the DTO except the interface extends Mongoose Document module. Next, we will create a service for CRUD operation of Mongoose schema. Create a new file for the service inside the article folder.

touch src/article/article.service.ts

Open and edit that file then add these lines of Typescript codes.

import { Inject, Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { ArticleDto } from './dto/article.dto';
import { Article } from './interfaces/article.interface';

@Injectable()
export class ArticleService {

  constructor(@Inject('ARTICLE_MODEL') private readonly articleModel: Model) {}

  async create(articleDto: ArticleDto): Promise {
    const createdArticle = new this.articleModel(articleDto);
    return await createdArticle.save();
  }

  async findAll(): Promise {
    return await this.articleModel.find().exec();
  }

  async find(id: string): Promise {
    return await this.articleModel.findById(id).exec();
  }

  async update(id: string, articleDto: ArticleDto): Promise {
    return await this.articleModel.findByIdAndUpdate(id, articleDto);
  }

  async delete(id: string, articleDto: ArticleDto): Promise {
    return await this.articleModel.findByIdAndRemove(id);
  }
}

Creating the Nestjs routes means to create a controller that will transform by the Nest.js as a router. Type this command to create a file for the controller inside the article folder.

touch src/article/article.controller.ts

Open and edit that file then add these lines of Typescript for all Post, Put, Get, and Delete routes.

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { ArticleDto } from './dto/article.dto';
import { ArticleService } from './article.service';
import { Article } from './interfaces/article.interface';

@Controller('article')
export class ArticleController {
    constructor(private readonly articleService: ArticleService) {}

    @Post()
        async create(@Body() articleDto: ArticleDto) {
        return this.articleService.create(articleDto);
    }

    @Get()
        async findAll(): Promise {
        return this.articleService.findAll();
    }

    @Get(':id')
        async find(@Param('id') id: string) {
        return this.articleService.find(id);
    }

    @Put(':id')
        async update(@Param('id') id: string, @Body() articleDto: ArticleDto) {
        return this.articleService.update(id, articleDto);
    }

    @Delete(':id')
        async delete(@Param('id') id: string, @Body() articleDto: ArticleDto) {
        return this.articleService.delete(id, articleDto);
    }
}

The routes for Post, Get, Put, Delete, Param, and Body marked by Nestjs annotation. Next, create a file inside the article folder for the provider that register a connection from the schema to the Database that handles by database module.

touch src/article/article.providers.ts

Open and edit that file the add these lines of Typescript codes.

import { Connection } from 'mongoose';
import { ArticleSchema } from './schemas/article.schemas';

export const articleProviders = [
    {
        provide: 'ARTICLE_MODEL',
        useFactory: (connection: Connection) => connection.model('Article', ArticleSchema),
        inject: ['DATABASE_CONNECTION'],
    },
];

Next, wrapping all required files to build an article module as the RESTful API by creating a file inside the article folder for the module.

touch src/article/article.module.ts

Open and edit that file then add these lines of Typescript codes.

import { Module } from '@nestjs/common';
import { ArticleController } from './article.controller';
import { ArticleService } from './article.service';
import { articleProviders } from './article.providers';
import { DatabaseModule } from '../database/database.module';

@Module({
    imports: [DatabaseModule],
    controllers: [ArticleController],
    providers: [ArticleService, ...articleProviders],
})
export class ArticleModule {}

Finally, register the article module to the main Nestjs module. Open and edit src/app.module.ts then add this import.

import { ArticleModule } from './article/article.module';

Add to the @Module imports array.

@Module({
  imports: [ArticleModule],
})

We have to use different URL for the RESTful API. For that, create a prefix /api for the RESTful API by open and edit src/main.ts then add this line before the port declaration.

app.setGlobalPrefix('/api');

Test the Nestjs RESTful API using the Postman

Now, to test the Nestjs RESTful API, we will use the Postman application. First, run again the Nestjs application after running the MongoDB server in the different terminal tab.

npm start

Next, open the Postman application then make a GET request like below with the results.

Building a web app

To make a GET request for single result by ID, change the URL to this.

http://localhost:3000/api/article/5d234c34420cfcf038ee72c9

You should see the single result of the request like below.

Building a web app

To make a POST request, change the method to POST then address to

[http://localhost:3000/api/article.](http://localhost:3000/api/article`.) Fill the body as raw data with this

JSON data.

{
    "title": "A test article",
    "author": "Me",
    "description": "The article that live in the Nestjs server",
    "content": "The article that live in the Nestjs server."
}

You will see this response for the successful POST request.

{
    "_id": "5d28759e4f5e134b379e0dbb",
    "title": "A test article",
    "author": "Me",
    "description": "The article that live in the Nestjs server",
    "content": "The article that live in the Nestjs server.",
    "updatedAt": "2019-07-12T11:57:18.205Z",
    "__v": 0
}

For the PUT and DELETE method, just change the method in the Postman and the URL using the ID parameter. If everything works fine then the Nestjs RESTful API is ready to use with Angular 8 application.

Create a New Angular 8 Application

Now, for the frontend, we will create a new Angular 8 application. We will put the Angular 8 project folder inside the root of Nestjs folder. Before creating the new Angular 8 application, update the Angular CLI to the latest version.

sudo npm install -g @angular/cli

Check the installed or update Angular version using this command.

ng --version
Angular CLI: 8.0.6

Next, type this command to create a new Angular 8 application inside the Nestjs root folder.

ng new client

Go to the newly created Angular 8 project.

cd client

Run the Angular 8 application for the first time to make sure everything is on the track.

ng serve

You will see the Angular 8 started page in the browser when you point the browser address to [http://localhost:4200.](http://localhost:4200`.)

Create Angular 8 Routes for Page Navigation

To create Angular 8 Routes for navigation between Angular 8 pages/component, add or generate all required component.

ng g component articles
ng g component show-article
ng g component add-article
ng g component edit-article

Next, open and edit src/app/app-routing.module.ts then add these imports.

import { ArticlesComponent } from './articles/articles.component';
import { ShowArticleComponent } from './show-article/show-article.component';
import { AddArticleComponent } from './add-article/add-article.component';
import { EditArticleComponent } from './edit-article/edit-article.component';

Add the route from the Angular 8 component to the routes constant variable.

const routes: Routes = [
  {
    path: 'articles',
    component: ArticlesComponent,
    data: { title: 'List of Articles' }
  },
  {
    path: 'show-article/:id',
    component: ShowArticleComponent,
    data: { title: 'Show Product' }
  },
  {
    path: 'add-article',
    component: AddArticleComponent,
    data: { title: 'Add Article' }
  },
  {
    path: 'edit-article/:id',
    component: EditArticleComponent,
    data: { title: 'Edit Article' }
  },
  { path: '',
    redirectTo: '/articles',
    pathMatch: 'full'
  }
];

Open and edit src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.


  

Open and edit src/app/app.component.scss then replace all SASS codes with this.

.container {
  padding: 20px;
}

Create an Angular 8 Service using HttpClient, Observable and RXJS

To access the Nestjs RESTful API from Angular 8, we need to create an Angular 8 service which will handle all POST, GET, UPDATE, DELETE requests. The response from the RESTful API emitted by Observable that can subscribe and read from the Components. For error handler and data Extraction, we will use RXJS Library. Before creating a service for RESTful API access, first, we have to install or register HttpClientModule. Open and edit src/app/app.module.ts then add this import.

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add it to @NgModule imports after BrowserModule.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule
],

We will use type specifier to get a typed result object. For that, create a new Typescript file src/app/article.ts then add these lines of Typescript codes.

export class Article {
  _id: string;
  title: string;
  author: string;
  description: string;
  content: string;
  updatedAt: Date;
}

Next, generate an Angular 8 service by typing this command.

ng g service api

Next, open and edit src/app/api.service.ts then add these imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Article } from './article';

Add these constants before the @Injectable.

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

We are using URL for API using /api/article because at the end we will integrate the Angular 8 application as the static files for the Nestjs Fastify server. Next, inject HttpClient module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

private handleError(operation = 'operation', result?: T) {
  return (error: any): Observable => {
    console.error(error); // log to console instead
    return of(result as T);
  };
}

Add the functions for all CRUD (create, read, update, delete) RESTful call of article data.

getArticles(): Observable {
  return this.http.get(apiUrl)
    .pipe(
      tap(article => console.log('fetched articles')),
      catchError(this.handleError('getArticles', []))
    );
}

getArticle(id: number): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.get(url).pipe(
    tap(_ => console.log(`fetched article id=${id}`)),
    catchError(this.handleError(`getArticle id=${id}`))
  );
}

addArticle(article: Article): Observable {
  return this.http.post(apiUrl, article, httpOptions).pipe(
    tap((art: Article) => console.log(`added article w/ id=${art._id}`)),
    catchError(this.handleError('addArticle'))
  );
}

updateArticle(id: any, article: Article): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.put(url, article, httpOptions).pipe(
    tap(_ => console.log(`updated article id=${id}`)),
    catchError(this.handleError('updateArticle'))
  );
}

deleteArticle(id: any): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.delete(url, httpOptions).pipe(
    tap(_ => console.log(`deleted article id=${id}`)),
    catchError(this.handleError('deleteArticle'))
  );
}

You can find more details about Angular 8 Observable and RXJS here.

Display List of Articles using Angular 8 Material

We will display the list of articles published from API Service. The data published from the API service read by subscribing as an Article model in the Angular 8 component. For that, open and edit src/app/articles/articles.component.ts then add these imports.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for the user interface (UI) we will use Angular 8 Material and CDK. There’s a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 8 Material.

ng add @angular/material

If there are questions like below, just use the default answer.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: h
ttps://material.angular.io?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular 8 Material components or modules to src/app/app.module.ts. Open and edit that file then add these imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, back to src/app/articles/articles.component.ts then add these imports.

import { Article } from '../article';

Declare the variables of Angular 8 Material Table Data Source before the constructor.

displayedColumns: string[] = ['title', 'author'];
data: Article[] = [];
isLoadingResults = true;

Modify the ngOnInit function to get list of articles immediately.

ngOnInit() {
  this.api.getArticles()
    .subscribe((res: any) => {
      this.data = res;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Next, open and edit src/app/articles/articles.component.html then replace all HTML tags with this Angular Material tags.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    add
  
  
    <table mat-table [dataSource]="data" class="example-table"
           matSort matSortActive="title" matSortDisableClear matSortDirection="asc">

      
      
        Title
        {{row.title}}
      

      
      
        Author
        $ {{row.author}}
      

      
      
    
  

Finally, to make a little UI adjustment, open and edit src/app/articles/articles.component.scss then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

table {
  width: 100%;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-rate-limit-reached {
  color: #980000;
  max-width: 360px;
  text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

.mat-flat-button {
  margin: 5px;
}

Show and Delete an Article Details using Angular 8 Material

To show article details after click or tap on the one of a row inside the Angular 8 Material table, open and edit src/app/show-article/show-article.component.ts then add these imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Article } from '../article';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold article data that get from the API.

getArticleDetails(id: any) {
  this.api.getArticle(id)
    .subscribe((data: any) => {
      this.article = data;
      console.log(this.article);
      this.isLoadingResults = false;
    });
}

Add a function for getting Article data from the API.

getArticleDetails(id: any) { this.api.getArticle(id)  .subscribe((data: any) => {   this.article = data;   console.log(this.article);   this.isLoadingResults = false;  }); }

Call that function when the component is initiated.

ngOnInit() {
  this.getArticleDetails(this.route.snapshot.params.id);
}

Add this function for delete article.

deleteArticle(id: any) {
  this.isLoadingResults = true;
  this.api.deleteArticle(id)
    .subscribe(res => {
        this.isLoadingResults = false;
        this.router.navigate(['/articles']);
      }, (err) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

For the view, open and edit src/app/article-detail/article-detail.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    list
  
  
    
      ## {{article.title}}

      {{article.author}} | {{article.updatedAt}}
    
    
      ### {{article.description}}

      
{{article.content}}

    
    
      edit
      delete
    
  

Finally, open and edit src/app/article-detail/article-detail.component.scss then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

Add an Article using Angular 8 Material

To create a form for adding an Article, open and edit src/app/add-article/add-article.component.ts then add these imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Inject above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

articleForm: FormGroup;
title = '';
author = '';
description = '';
content = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Next, create a class for MyErrorStateMatcher before the the main class @Components.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Add initial validation for each field.

ngOnInit() {
  this.articleForm = this.formBuilder.group({
    'title' : [null, Validators.required],
    'author' : [null, Validators.required],
    'description' : [null, Validators.required],
    'content' : [null, Validators.required]
  });
}

Create a function for submitting or POST article form.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.addArticle(this.articleForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/show-article', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      });
}

Next, open and edit src/app/add-article/add-article.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    list
  
  
    
      
        <input matInput placeholder="Title" formControlName="title"
               [errorStateMatcher]="matcher">
        
          Please enter Title
        
      
      
        <input matInput placeholder="Author" formControlName="author"
               [errorStateMatcher]="matcher">
        
          Please enter Author
        
      
      
        <input matInput placeholder="Description" formControlName="description"
               [errorStateMatcher]="matcher">
        
          Please enter Description
        
      
      
        <textarea matInput placeholder="Content" formControlName="content"
               [errorStateMatcher]="matcher">
        
          Please enter Content
        
      
      
        save
      
    
  

Finally, open and edit src/app/article-add/article-add.component.scss then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Edit an Article using Angular 8 Material

We have put an edit button inside the Article Detail component for call Edit page. Now, open and edit src/app/edit-article/edit-article.component.ts then add these imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Next, create a class for ErrorStateMatcher before the main class @Compoents.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Inject the imported modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the articles form before the constructor.

articleForm: FormGroup;
_id = '';
title = '';
author = '';
description = '';
content = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Next, add validation for all fields when the component is initiated.

ngOnInit() {
  this.getArticle(this.route.snapshot.params.id);
  this.articleForm = this.formBuilder.group({
    'title' : [null, Validators.required],
    'author' : [null, Validators.required],
    'description' : [null, Validators.required],
    'content' : [null, Validators.required]
  });
}

Create a function for getting article data that filled to each form fields.

getArticle(id: any) {
  this.api.getArticle(id).subscribe((data: any) => {
    this._id = data._id;
    this.articleForm.setValue({
      title: data.title,
      author: data.author,
      description: data.description,
      content: data.content
    });
  });
}

Create a function to update the article changes.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.updateArticle(this._id, this.articleForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/show-article', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

Add a function for handling the show article details button.

articleDetails() {
  this.router.navigate(['/show-article', this._id]);
}

Next, open and edit src/app/edit-article/edit-article.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    info
  
  
    
      
        <input matInput placeholder="Title" formControlName="title"
               [errorStateMatcher]="matcher">
        
          Please enter Title
        
      
      
        <input matInput placeholder="Author" formControlName="author"
               [errorStateMatcher]="matcher">
        
          Please enter Author
        
      
      
        <input matInput placeholder="Description" formControlName="description"
               [errorStateMatcher]="matcher">
        
          Please enter Description
        
      
      
        <textarea matInput placeholder="Content" formControlName="content"
               [errorStateMatcher]="matcher">
        
          Please enter Content
        
      
      
        save
      
    
  

Finally, open and edit src/app/edit-article/edit-article.component.scss then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Integrate, Test and Run the Complete Nestjs, Fastify, MongoDB and Angular 8 Web Apps

To integrate the Angular 8 application into the Nestjs/Fastify application, open and edit src/main.js then add this import.

import { join } from 'path';

Add this line before the port listener line.

app.useStaticAssets({
  root: join(__dirname, '..', 'client/dist/client'),
  prefix: '/',
});

Next, make sure the MongoDB server is running then build the Angular 8 application.

cd client
ng build --prod

The Angular 8 build should be located in client/dist/client folder. Next, back to the root of Nestjs folder then run again the Nestjs application.

npm start

Open the browser and go to [http://localhost:3000](http://localhost:3000`) and you should see this application.

That it’s, the complete step by step tutorial of building a web app using Nestjs, Fastify, MongoDB and Angular 8. You can find the full source code in our GitHub.

#angular #mongodb #node-js #web-development

What is GEEK

Buddha Community

Building a web app using Nestjs, Fastify, MongoDB and Angular 8

Janis Leicis

1563042126

more more full stack tutorials with angular and nest.js )

Top Enterprise Angular Web Apps Development Company in USA

AppClues Infotech is one of the leading Enterprise Angular Web Apps Development Company in USA. Our dedicated & highly experienced Angular app developers build top-grade Angular apps for your business with immersive technology & superior functionalities.

For more info:
Website: https://www.appcluesinfotech.com/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910

#top enterprise angular web apps development company in usa #enterprise angular web apps development #hire enterprise angular web apps developers #best enterprise angular web app services #custom enterprise angular web apps solution #professional enterprise angular web apps developers

Chloe  Butler

Chloe Butler

1667425440

Pdf2gerb: Perl Script Converts PDF Files to Gerber format

pdf2gerb

Perl script converts PDF files to Gerber format

Pdf2Gerb generates Gerber 274X photoplotting and Excellon drill files from PDFs of a PCB. Up to three PDFs are used: the top copper layer, the bottom copper layer (for 2-sided PCBs), and an optional silk screen layer. The PDFs can be created directly from any PDF drawing software, or a PDF print driver can be used to capture the Print output if the drawing software does not directly support output to PDF.

The general workflow is as follows:

  1. Design the PCB using your favorite CAD or drawing software.
  2. Print the top and bottom copper and top silk screen layers to a PDF file.
  3. Run Pdf2Gerb on the PDFs to create Gerber and Excellon files.
  4. Use a Gerber viewer to double-check the output against the original PCB design.
  5. Make adjustments as needed.
  6. Submit the files to a PCB manufacturer.

Please note that Pdf2Gerb does NOT perform DRC (Design Rule Checks), as these will vary according to individual PCB manufacturer conventions and capabilities. Also note that Pdf2Gerb is not perfect, so the output files must always be checked before submitting them. As of version 1.6, Pdf2Gerb supports most PCB elements, such as round and square pads, round holes, traces, SMD pads, ground planes, no-fill areas, and panelization. However, because it interprets the graphical output of a Print function, there are limitations in what it can recognize (or there may be bugs).

See docs/Pdf2Gerb.pdf for install/setup, config, usage, and other info.


pdf2gerb_cfg.pm

#Pdf2Gerb config settings:
#Put this file in same folder/directory as pdf2gerb.pl itself (global settings),
#or copy to another folder/directory with PDFs if you want PCB-specific settings.
#There is only one user of this file, so we don't need a custom package or namespace.
#NOTE: all constants defined in here will be added to main namespace.
#package pdf2gerb_cfg;

use strict; #trap undef vars (easier debug)
use warnings; #other useful info (easier debug)


##############################################################################################
#configurable settings:
#change values here instead of in main pfg2gerb.pl file

use constant WANT_COLORS => ($^O !~ m/Win/); #ANSI colors no worky on Windows? this must be set < first DebugPrint() call

#just a little warning; set realistic expectations:
#DebugPrint("${\(CYAN)}Pdf2Gerb.pl ${\(VERSION)}, $^O O/S\n${\(YELLOW)}${\(BOLD)}${\(ITALIC)}This is EXPERIMENTAL software.  \nGerber files MAY CONTAIN ERRORS.  Please CHECK them before fabrication!${\(RESET)}", 0); #if WANT_DEBUG

use constant METRIC => FALSE; #set to TRUE for metric units (only affect final numbers in output files, not internal arithmetic)
use constant APERTURE_LIMIT => 0; #34; #max #apertures to use; generate warnings if too many apertures are used (0 to not check)
use constant DRILL_FMT => '2.4'; #'2.3'; #'2.4' is the default for PCB fab; change to '2.3' for CNC

use constant WANT_DEBUG => 0; #10; #level of debug wanted; higher == more, lower == less, 0 == none
use constant GERBER_DEBUG => 0; #level of debug to include in Gerber file; DON'T USE FOR FABRICATION
use constant WANT_STREAMS => FALSE; #TRUE; #save decompressed streams to files (for debug)
use constant WANT_ALLINPUT => FALSE; #TRUE; #save entire input stream (for debug ONLY)

#DebugPrint(sprintf("${\(CYAN)}DEBUG: stdout %d, gerber %d, want streams? %d, all input? %d, O/S: $^O, Perl: $]${\(RESET)}\n", WANT_DEBUG, GERBER_DEBUG, WANT_STREAMS, WANT_ALLINPUT), 1);
#DebugPrint(sprintf("max int = %d, min int = %d\n", MAXINT, MININT), 1); 

#define standard trace and pad sizes to reduce scaling or PDF rendering errors:
#This avoids weird aperture settings and replaces them with more standardized values.
#(I'm not sure how photoplotters handle strange sizes).
#Fewer choices here gives more accurate mapping in the final Gerber files.
#units are in inches
use constant TOOL_SIZES => #add more as desired
(
#round or square pads (> 0) and drills (< 0):
    .010, -.001,  #tiny pads for SMD; dummy drill size (too small for practical use, but needed so StandardTool will use this entry)
    .031, -.014,  #used for vias
    .041, -.020,  #smallest non-filled plated hole
    .051, -.025,
    .056, -.029,  #useful for IC pins
    .070, -.033,
    .075, -.040,  #heavier leads
#    .090, -.043,  #NOTE: 600 dpi is not high enough resolution to reliably distinguish between .043" and .046", so choose 1 of the 2 here
    .100, -.046,
    .115, -.052,
    .130, -.061,
    .140, -.067,
    .150, -.079,
    .175, -.088,
    .190, -.093,
    .200, -.100,
    .220, -.110,
    .160, -.125,  #useful for mounting holes
#some additional pad sizes without holes (repeat a previous hole size if you just want the pad size):
    .090, -.040,  #want a .090 pad option, but use dummy hole size
    .065, -.040, #.065 x .065 rect pad
    .035, -.040, #.035 x .065 rect pad
#traces:
    .001,  #too thin for real traces; use only for board outlines
    .006,  #minimum real trace width; mainly used for text
    .008,  #mainly used for mid-sized text, not traces
    .010,  #minimum recommended trace width for low-current signals
    .012,
    .015,  #moderate low-voltage current
    .020,  #heavier trace for power, ground (even if a lighter one is adequate)
    .025,
    .030,  #heavy-current traces; be careful with these ones!
    .040,
    .050,
    .060,
    .080,
    .100,
    .120,
);
#Areas larger than the values below will be filled with parallel lines:
#This cuts down on the number of aperture sizes used.
#Set to 0 to always use an aperture or drill, regardless of size.
use constant { MAX_APERTURE => max((TOOL_SIZES)) + .004, MAX_DRILL => -min((TOOL_SIZES)) + .004 }; #max aperture and drill sizes (plus a little tolerance)
#DebugPrint(sprintf("using %d standard tool sizes: %s, max aper %.3f, max drill %.3f\n", scalar((TOOL_SIZES)), join(", ", (TOOL_SIZES)), MAX_APERTURE, MAX_DRILL), 1);

#NOTE: Compare the PDF to the original CAD file to check the accuracy of the PDF rendering and parsing!
#for example, the CAD software I used generated the following circles for holes:
#CAD hole size:   parsed PDF diameter:      error:
#  .014                .016                +.002
#  .020                .02267              +.00267
#  .025                .026                +.001
#  .029                .03167              +.00267
#  .033                .036                +.003
#  .040                .04267              +.00267
#This was usually ~ .002" - .003" too big compared to the hole as displayed in the CAD software.
#To compensate for PDF rendering errors (either during CAD Print function or PDF parsing logic), adjust the values below as needed.
#units are pixels; for example, a value of 2.4 at 600 dpi = .0004 inch, 2 at 600 dpi = .0033"
use constant
{
    HOLE_ADJUST => -0.004 * 600, #-2.6, #holes seemed to be slightly oversized (by .002" - .004"), so shrink them a little
    RNDPAD_ADJUST => -0.003 * 600, #-2, #-2.4, #round pads seemed to be slightly oversized, so shrink them a little
    SQRPAD_ADJUST => +0.001 * 600, #+.5, #square pads are sometimes too small by .00067, so bump them up a little
    RECTPAD_ADJUST => 0, #(pixels) rectangular pads seem to be okay? (not tested much)
    TRACE_ADJUST => 0, #(pixels) traces seemed to be okay?
    REDUCE_TOLERANCE => .001, #(inches) allow this much variation when reducing circles and rects
};

#Also, my CAD's Print function or the PDF print driver I used was a little off for circles, so define some additional adjustment values here:
#Values are added to X/Y coordinates; units are pixels; for example, a value of 1 at 600 dpi would be ~= .002 inch
use constant
{
    CIRCLE_ADJUST_MINX => 0,
    CIRCLE_ADJUST_MINY => -0.001 * 600, #-1, #circles were a little too high, so nudge them a little lower
    CIRCLE_ADJUST_MAXX => +0.001 * 600, #+1, #circles were a little too far to the left, so nudge them a little to the right
    CIRCLE_ADJUST_MAXY => 0,
    SUBST_CIRCLE_CLIPRECT => FALSE, #generate circle and substitute for clip rects (to compensate for the way some CAD software draws circles)
    WANT_CLIPRECT => TRUE, #FALSE, #AI doesn't need clip rect at all? should be on normally?
    RECT_COMPLETION => FALSE, #TRUE, #fill in 4th side of rect when 3 sides found
};

#allow .012 clearance around pads for solder mask:
#This value effectively adjusts pad sizes in the TOOL_SIZES list above (only for solder mask layers).
use constant SOLDER_MARGIN => +.012; #units are inches

#line join/cap styles:
use constant
{
    CAP_NONE => 0, #butt (none); line is exact length
    CAP_ROUND => 1, #round cap/join; line overhangs by a semi-circle at either end
    CAP_SQUARE => 2, #square cap/join; line overhangs by a half square on either end
    CAP_OVERRIDE => FALSE, #cap style overrides drawing logic
};
    
#number of elements in each shape type:
use constant
{
    RECT_SHAPELEN => 6, #x0, y0, x1, y1, count, "rect" (start, end corners)
    LINE_SHAPELEN => 6, #x0, y0, x1, y1, count, "line" (line seg)
    CURVE_SHAPELEN => 10, #xstart, ystart, x0, y0, x1, y1, xend, yend, count, "curve" (bezier 2 points)
    CIRCLE_SHAPELEN => 5, #x, y, 5, count, "circle" (center + radius)
};
#const my %SHAPELEN =
#Readonly my %SHAPELEN =>
our %SHAPELEN =
(
    rect => RECT_SHAPELEN,
    line => LINE_SHAPELEN,
    curve => CURVE_SHAPELEN,
    circle => CIRCLE_SHAPELEN,
);

#panelization:
#This will repeat the entire body the number of times indicated along the X or Y axes (files grow accordingly).
#Display elements that overhang PCB boundary can be squashed or left as-is (typically text or other silk screen markings).
#Set "overhangs" TRUE to allow overhangs, FALSE to truncate them.
#xpad and ypad allow margins to be added around outer edge of panelized PCB.
use constant PANELIZE => {'x' => 1, 'y' => 1, 'xpad' => 0, 'ypad' => 0, 'overhangs' => TRUE}; #number of times to repeat in X and Y directions

# Set this to 1 if you need TurboCAD support.
#$turboCAD = FALSE; #is this still needed as an option?

#CIRCAD pad generation uses an appropriate aperture, then moves it (stroke) "a little" - we use this to find pads and distinguish them from PCB holes. 
use constant PAD_STROKE => 0.3; #0.0005 * 600; #units are pixels
#convert very short traces to pads or holes:
use constant TRACE_MINLEN => .001; #units are inches
#use constant ALWAYS_XY => TRUE; #FALSE; #force XY even if X or Y doesn't change; NOTE: needs to be TRUE for all pads to show in FlatCAM and ViewPlot
use constant REMOVE_POLARITY => FALSE; #TRUE; #set to remove subtractive (negative) polarity; NOTE: must be FALSE for ground planes

#PDF uses "points", each point = 1/72 inch
#combined with a PDF scale factor of .12, this gives 600 dpi resolution (1/72 * .12 = 600 dpi)
use constant INCHES_PER_POINT => 1/72; #0.0138888889; #multiply point-size by this to get inches

# The precision used when computing a bezier curve. Higher numbers are more precise but slower (and generate larger files).
#$bezierPrecision = 100;
use constant BEZIER_PRECISION => 36; #100; #use const; reduced for faster rendering (mainly used for silk screen and thermal pads)

# Ground planes and silk screen or larger copper rectangles or circles are filled line-by-line using this resolution.
use constant FILL_WIDTH => .01; #fill at most 0.01 inch at a time

# The max number of characters to read into memory
use constant MAX_BYTES => 10 * M; #bumped up to 10 MB, use const

use constant DUP_DRILL1 => TRUE; #FALSE; #kludge: ViewPlot doesn't load drill files that are too small so duplicate first tool

my $runtime = time(); #Time::HiRes::gettimeofday(); #measure my execution time

print STDERR "Loaded config settings from '${\(__FILE__)}'.\n";
1; #last value must be truthful to indicate successful load


#############################################################################################
#junk/experiment:

#use Package::Constants;
#use Exporter qw(import); #https://perldoc.perl.org/Exporter.html

#my $caller = "pdf2gerb::";

#sub cfg
#{
#    my $proto = shift;
#    my $class = ref($proto) || $proto;
#    my $settings =
#    {
#        $WANT_DEBUG => 990, #10; #level of debug wanted; higher == more, lower == less, 0 == none
#    };
#    bless($settings, $class);
#    return $settings;
#}

#use constant HELLO => "hi there2"; #"main::HELLO" => "hi there";
#use constant GOODBYE => 14; #"main::GOODBYE" => 12;

#print STDERR "read cfg file\n";

#our @EXPORT_OK = Package::Constants->list(__PACKAGE__); #https://www.perlmonks.org/?node_id=1072691; NOTE: "_OK" skips short/common names

#print STDERR scalar(@EXPORT_OK) . " consts exported:\n";
#foreach(@EXPORT_OK) { print STDERR "$_\n"; }
#my $val = main::thing("xyz");
#print STDERR "caller gave me $val\n";
#foreach my $arg (@ARGV) { print STDERR "arg $arg\n"; }

Download Details:

Author: swannman
Source Code: https://github.com/swannman/pdf2gerb

License: GPL-3.0 license

#perl 

Fredy  Larson

Fredy Larson

1595059664

How long does it take to develop/build an app?

With more of us using smartphones, the popularity of mobile applications has exploded. In the digital era, the number of people looking for products and services online is growing rapidly. Smartphone owners look for mobile applications that give them quick access to companies’ products and services. As a result, mobile apps provide customers with a lot of benefits in just one device.

Likewise, companies use mobile apps to increase customer loyalty and improve their services. Mobile Developers are in high demand as companies use apps not only to create brand awareness but also to gather information. For that reason, mobile apps are used as tools to collect valuable data from customers to help companies improve their offer.

There are many types of mobile applications, each with its own advantages. For example, native apps perform better, while web apps don’t need to be customized for the platform or operating system (OS). Likewise, hybrid apps provide users with comfortable user experience. However, you may be wondering how long it takes to develop an app.

To give you an idea of how long the app development process takes, here’s a short guide.

App Idea & Research

app-idea-research

_Average time spent: two to five weeks _

This is the initial stage and a crucial step in setting the project in the right direction. In this stage, you brainstorm ideas and select the best one. Apart from that, you’ll need to do some research to see if your idea is viable. Remember that coming up with an idea is easy; the hard part is to make it a reality.

All your ideas may seem viable, but you still have to run some tests to keep it as real as possible. For that reason, when Web Developers are building a web app, they analyze the available ideas to see which one is the best match for the targeted audience.

Targeting the right audience is crucial when you are developing an app. It saves time when shaping the app in the right direction as you have a clear set of objectives. Likewise, analyzing how the app affects the market is essential. During the research process, App Developers must gather information about potential competitors and threats. This helps the app owners develop strategies to tackle difficulties that come up after the launch.

The research process can take several weeks, but it determines how successful your app can be. For that reason, you must take your time to know all the weaknesses and strengths of the competitors, possible app strategies, and targeted audience.

The outcomes of this stage are app prototypes and the minimum feasible product.

#android app #frontend #ios app #minimum viable product (mvp) #mobile app development #web development #android app development #app development #app development for ios and android #app development process #ios and android app development #ios app development #stages in app development

Custom AngularJS Web App Development Company in USA

Looking for the best custom AngularJS app development company? AppClues Infotech is a top-rated AngularJS app development company in USA producing robust, highly interactive and data-driven AngularJS web and mobile applications with advanced features & technologies.

For more info:
Website: https://www.appcluesinfotech.com/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910

#custom angular js web app development company in usa #best angular js app development company in usa #hire angular js app developers in usa #top angular js app development company #professional angular js app developers #leading angular js app development agency

Daisy Rees

Daisy Rees

1563155047

Building a web app using Nestjs, Fastify, MongoDB and Angular 8

Learn to build the efficient, reliable and scalable server-side web app using Nestjs, Fastify, MongoDB and Angular 8. Nest.js is a complete development kit for building scalable server-side applications. The Fastify is alternative for Express.js as HTTP-Server dan claimed faster than Express.js. Angular 8 is the latest, smaller build and faster Angular version. So, this is the perfect combination for building better web apps.

As usual, we will learn to build the efficient, reliable and scalable server-side web app through the step by step from the scratch. Starting to build the backend application using Nestjs and Fastify then complete with Angular 8 as frontend application. The Angular 8 application access the data layer via RESTful API that provided by Nestjs which served by prefix URL /api. Nest.js accessing the MongoDB via Mongoose.js. All required libraries and modules that use by the backend already prepared very well by The Nestjs.

The following libraries, frameworks, and modules are required for this tutorial:

Before move further, make sure we have installed the Node.js and MongoDB server. To check if they were installed, type these commands in the terminal or command line.

node -v
v10.15.1
npm -v
6.9.0
mongo --version
MongoDB shell version: 3.2.11

Install Nestjs and Create a New Application

We will start the steps by building a backend or server-side application using Nestjs. For that, we have to install the Nest.js first by typing this command in the terminal or command line.

sudo npm i -g @nestjs/cli

You can run that command without sudo in the command line. Next, to create a new server-side application, type this command.

nest new nest-angular8

Next, go to the newly created project folder then run the application for the first time by type this command.

npm start

Open the browser then go to [http://localhost:3000](http://localhost:3000) and you should see the text Hello world!` in the web browser. Next, install the Fastify.js to increase the performance of Nestjs application because by default Nestjs using Express.js. Type this command to install it.

npm i --save @nestjs/platform-fastify

Next, open and edit src/main.ts then modify/add these imports.

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

Change the bootstrap function to these lines of codes.

async function bootstrap() {
  const app = await NestFactory.create(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000);
}
bootstrap();

Now, the Fastify.js will run as the default engine for Nestjs application.

Install and Configure Mongoose.js

We will use Mongoose.js as ORM for MongoDB. For that, type this command to install the Nest.js Mongoose.js modules.

npm install --save @nestjs/mongoose mongoose

Next, create a database folder inside the src folder. We will create all folder and files manually, if you like, you can generate them using Nestjs CLI.

mkdir database

Add 2 files to the database folder.

touch src/database/database.module.ts
touch src/database/database.provider.ts

Next, open and edit src/database/database.provider.ts then add these lines of codes.

import * as mongoose from 'mongoose';

export const databaseProviders = [
    {
        provide: 'DATABASE_CONNECTION',
        useFactory: async (): Promise =>
        await mongoose.connect('mongodb://localhost/angular8-crud', { useNewUrlParser: true, useFindAndModify: false }),
    },
];

Provider in this database module is used for connecting the Nest application with MongoDB database. Next, open and edit src/database/database.module.ts then add these lines of codes.

import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
    providers: [...databaseProviders],
    exports: [...databaseProviders],
})
export class DatabaseModule {}

Create a Mongoose Schema

The next steps to create a CRUD RESTful API is to create a Mongoose schema or model. We will make a modular application for each object, so, for the Article object, we will create a folder for it first.

mkdir src/article

Next, create a schema folder and a schema file inside that folder.

mkdir src/article/schemas
touch src/article/schemas/article.schemas.ts

Next, open and edit that file then add these lines of Typescript codes.

import * as mongoose from 'mongoose';

export const ArticleSchema = new mongoose.Schema({
    title: { type: String, required: true },
    author: { type: String, required: true },
    description: { type: String, required: true },
    content: { type: String, required: true },
    updatedAt: { type: Date, default: Date.now },
});

Next, create a provider file to register the Mongoose schema as a model.

touch src/article/article.providers.ts

Open and edit that file then add these lines of Typescript codes.

import { Connection } from 'mongoose';
import { ArticleSchema } from './schemas/article.schemas';

export const articleProviders = [
    {
        provide: 'ARTICLE_MODEL',
        useFactory: (connection: Connection) => connection.model('Article', ArticleSchema),
        inject: ['DATABASE_CONNECTION'],
    },
];

Create the Routes for Article

Creating RESTful API using Nestjs similar to ASP Net Core Web API, especially when interacting with the Database. We have to create DTO and Interface to access Mongoose schema. Next, create a folder and a file for DTO inside article folder.

mkdir src/article/dto
touch src/article/dto/article.dto.ts

Open and edit that file then add these lines of Typescript codes.

export class ArticleDto {
    readonly title: string;
    readonly author: string;
    readonly description: string;
    readonly content: string;
}

Next, create a folder and file for the interface inside the article folder.

mkdir src/article/interfaces
touch src/article/interfaces/article.interface.ts

Open and edit that file then add these lines of Typescript codes.

import { Document } from 'mongoose';

export interface Article extends Document {
    readonly title: string;
    readonly author: string;
    readonly description: string;
    readonly content: string;
}

That interface similar with the DTO except the interface extends Mongoose Document module. Next, we will create a service for CRUD operation of Mongoose schema. Create a new file for the service inside the article folder.

touch src/article/article.service.ts

Open and edit that file then add these lines of Typescript codes.

import { Inject, Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { ArticleDto } from './dto/article.dto';
import { Article } from './interfaces/article.interface';

@Injectable()
export class ArticleService {

  constructor(@Inject('ARTICLE_MODEL') private readonly articleModel: Model) {}

  async create(articleDto: ArticleDto): Promise {
    const createdArticle = new this.articleModel(articleDto);
    return await createdArticle.save();
  }

  async findAll(): Promise {
    return await this.articleModel.find().exec();
  }

  async find(id: string): Promise {
    return await this.articleModel.findById(id).exec();
  }

  async update(id: string, articleDto: ArticleDto): Promise {
    return await this.articleModel.findByIdAndUpdate(id, articleDto);
  }

  async delete(id: string, articleDto: ArticleDto): Promise {
    return await this.articleModel.findByIdAndRemove(id);
  }
}

Creating the Nestjs routes means to create a controller that will transform by the Nest.js as a router. Type this command to create a file for the controller inside the article folder.

touch src/article/article.controller.ts

Open and edit that file then add these lines of Typescript for all Post, Put, Get, and Delete routes.

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { ArticleDto } from './dto/article.dto';
import { ArticleService } from './article.service';
import { Article } from './interfaces/article.interface';

@Controller('article')
export class ArticleController {
    constructor(private readonly articleService: ArticleService) {}

    @Post()
        async create(@Body() articleDto: ArticleDto) {
        return this.articleService.create(articleDto);
    }

    @Get()
        async findAll(): Promise {
        return this.articleService.findAll();
    }

    @Get(':id')
        async find(@Param('id') id: string) {
        return this.articleService.find(id);
    }

    @Put(':id')
        async update(@Param('id') id: string, @Body() articleDto: ArticleDto) {
        return this.articleService.update(id, articleDto);
    }

    @Delete(':id')
        async delete(@Param('id') id: string, @Body() articleDto: ArticleDto) {
        return this.articleService.delete(id, articleDto);
    }
}

The routes for Post, Get, Put, Delete, Param, and Body marked by Nestjs annotation. Next, create a file inside the article folder for the provider that register a connection from the schema to the Database that handles by database module.

touch src/article/article.providers.ts

Open and edit that file the add these lines of Typescript codes.

import { Connection } from 'mongoose';
import { ArticleSchema } from './schemas/article.schemas';

export const articleProviders = [
    {
        provide: 'ARTICLE_MODEL',
        useFactory: (connection: Connection) => connection.model('Article', ArticleSchema),
        inject: ['DATABASE_CONNECTION'],
    },
];

Next, wrapping all required files to build an article module as the RESTful API by creating a file inside the article folder for the module.

touch src/article/article.module.ts

Open and edit that file then add these lines of Typescript codes.

import { Module } from '@nestjs/common';
import { ArticleController } from './article.controller';
import { ArticleService } from './article.service';
import { articleProviders } from './article.providers';
import { DatabaseModule } from '../database/database.module';

@Module({
    imports: [DatabaseModule],
    controllers: [ArticleController],
    providers: [ArticleService, ...articleProviders],
})
export class ArticleModule {}

Finally, register the article module to the main Nestjs module. Open and edit src/app.module.ts then add this import.

import { ArticleModule } from './article/article.module';

Add to the @Module imports array.

@Module({
  imports: [ArticleModule],
})

We have to use different URL for the RESTful API. For that, create a prefix /api for the RESTful API by open and edit src/main.ts then add this line before the port declaration.

app.setGlobalPrefix('/api');

Test the Nestjs RESTful API using the Postman

Now, to test the Nestjs RESTful API, we will use the Postman application. First, run again the Nestjs application after running the MongoDB server in the different terminal tab.

npm start

Next, open the Postman application then make a GET request like below with the results.

Building a web app

To make a GET request for single result by ID, change the URL to this.

http://localhost:3000/api/article/5d234c34420cfcf038ee72c9

You should see the single result of the request like below.

Building a web app

To make a POST request, change the method to POST then address to

[http://localhost:3000/api/article.](http://localhost:3000/api/article`.) Fill the body as raw data with this

JSON data.

{
    "title": "A test article",
    "author": "Me",
    "description": "The article that live in the Nestjs server",
    "content": "The article that live in the Nestjs server."
}

You will see this response for the successful POST request.

{
    "_id": "5d28759e4f5e134b379e0dbb",
    "title": "A test article",
    "author": "Me",
    "description": "The article that live in the Nestjs server",
    "content": "The article that live in the Nestjs server.",
    "updatedAt": "2019-07-12T11:57:18.205Z",
    "__v": 0
}

For the PUT and DELETE method, just change the method in the Postman and the URL using the ID parameter. If everything works fine then the Nestjs RESTful API is ready to use with Angular 8 application.

Create a New Angular 8 Application

Now, for the frontend, we will create a new Angular 8 application. We will put the Angular 8 project folder inside the root of Nestjs folder. Before creating the new Angular 8 application, update the Angular CLI to the latest version.

sudo npm install -g @angular/cli

Check the installed or update Angular version using this command.

ng --version
Angular CLI: 8.0.6

Next, type this command to create a new Angular 8 application inside the Nestjs root folder.

ng new client

Go to the newly created Angular 8 project.

cd client

Run the Angular 8 application for the first time to make sure everything is on the track.

ng serve

You will see the Angular 8 started page in the browser when you point the browser address to [http://localhost:4200.](http://localhost:4200`.)

Create Angular 8 Routes for Page Navigation

To create Angular 8 Routes for navigation between Angular 8 pages/component, add or generate all required component.

ng g component articles
ng g component show-article
ng g component add-article
ng g component edit-article

Next, open and edit src/app/app-routing.module.ts then add these imports.

import { ArticlesComponent } from './articles/articles.component';
import { ShowArticleComponent } from './show-article/show-article.component';
import { AddArticleComponent } from './add-article/add-article.component';
import { EditArticleComponent } from './edit-article/edit-article.component';

Add the route from the Angular 8 component to the routes constant variable.

const routes: Routes = [
  {
    path: 'articles',
    component: ArticlesComponent,
    data: { title: 'List of Articles' }
  },
  {
    path: 'show-article/:id',
    component: ShowArticleComponent,
    data: { title: 'Show Product' }
  },
  {
    path: 'add-article',
    component: AddArticleComponent,
    data: { title: 'Add Article' }
  },
  {
    path: 'edit-article/:id',
    component: EditArticleComponent,
    data: { title: 'Edit Article' }
  },
  { path: '',
    redirectTo: '/articles',
    pathMatch: 'full'
  }
];

Open and edit src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.


  

Open and edit src/app/app.component.scss then replace all SASS codes with this.

.container {
  padding: 20px;
}

Create an Angular 8 Service using HttpClient, Observable and RXJS

To access the Nestjs RESTful API from Angular 8, we need to create an Angular 8 service which will handle all POST, GET, UPDATE, DELETE requests. The response from the RESTful API emitted by Observable that can subscribe and read from the Components. For error handler and data Extraction, we will use RXJS Library. Before creating a service for RESTful API access, first, we have to install or register HttpClientModule. Open and edit src/app/app.module.ts then add this import.

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add it to @NgModule imports after BrowserModule.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule
],

We will use type specifier to get a typed result object. For that, create a new Typescript file src/app/article.ts then add these lines of Typescript codes.

export class Article {
  _id: string;
  title: string;
  author: string;
  description: string;
  content: string;
  updatedAt: Date;
}

Next, generate an Angular 8 service by typing this command.

ng g service api

Next, open and edit src/app/api.service.ts then add these imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Article } from './article';

Add these constants before the @Injectable.

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

We are using URL for API using /api/article because at the end we will integrate the Angular 8 application as the static files for the Nestjs Fastify server. Next, inject HttpClient module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

private handleError(operation = 'operation', result?: T) {
  return (error: any): Observable => {
    console.error(error); // log to console instead
    return of(result as T);
  };
}

Add the functions for all CRUD (create, read, update, delete) RESTful call of article data.

getArticles(): Observable {
  return this.http.get(apiUrl)
    .pipe(
      tap(article => console.log('fetched articles')),
      catchError(this.handleError('getArticles', []))
    );
}

getArticle(id: number): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.get(url).pipe(
    tap(_ => console.log(`fetched article id=${id}`)),
    catchError(this.handleError(`getArticle id=${id}`))
  );
}

addArticle(article: Article): Observable {
  return this.http.post(apiUrl, article, httpOptions).pipe(
    tap((art: Article) => console.log(`added article w/ id=${art._id}`)),
    catchError(this.handleError('addArticle'))
  );
}

updateArticle(id: any, article: Article): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.put(url, article, httpOptions).pipe(
    tap(_ => console.log(`updated article id=${id}`)),
    catchError(this.handleError('updateArticle'))
  );
}

deleteArticle(id: any): Observable {
  const url = `${apiUrl}/${id}`;
  return this.http.delete(url, httpOptions).pipe(
    tap(_ => console.log(`deleted article id=${id}`)),
    catchError(this.handleError('deleteArticle'))
  );
}

You can find more details about Angular 8 Observable and RXJS here.

Display List of Articles using Angular 8 Material

We will display the list of articles published from API Service. The data published from the API service read by subscribing as an Article model in the Angular 8 component. For that, open and edit src/app/articles/articles.component.ts then add these imports.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for the user interface (UI) we will use Angular 8 Material and CDK. There’s a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 8 Material.

ng add @angular/material

If there are questions like below, just use the default answer.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: h
ttps://material.angular.io?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular 8 Material components or modules to src/app/app.module.ts. Open and edit that file then add these imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, back to src/app/articles/articles.component.ts then add these imports.

import { Article } from '../article';

Declare the variables of Angular 8 Material Table Data Source before the constructor.

displayedColumns: string[] = ['title', 'author'];
data: Article[] = [];
isLoadingResults = true;

Modify the ngOnInit function to get list of articles immediately.

ngOnInit() {
  this.api.getArticles()
    .subscribe((res: any) => {
      this.data = res;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Next, open and edit src/app/articles/articles.component.html then replace all HTML tags with this Angular Material tags.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    add
  
  
    <table mat-table [dataSource]="data" class="example-table"
           matSort matSortActive="title" matSortDisableClear matSortDirection="asc">

      
      
        Title
        {{row.title}}
      

      
      
        Author
        $ {{row.author}}
      

      
      
    
  

Finally, to make a little UI adjustment, open and edit src/app/articles/articles.component.scss then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

table {
  width: 100%;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-rate-limit-reached {
  color: #980000;
  max-width: 360px;
  text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

.mat-flat-button {
  margin: 5px;
}

Show and Delete an Article Details using Angular 8 Material

To show article details after click or tap on the one of a row inside the Angular 8 Material table, open and edit src/app/show-article/show-article.component.ts then add these imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Article } from '../article';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold article data that get from the API.

getArticleDetails(id: any) {
  this.api.getArticle(id)
    .subscribe((data: any) => {
      this.article = data;
      console.log(this.article);
      this.isLoadingResults = false;
    });
}

Add a function for getting Article data from the API.

getArticleDetails(id: any) { this.api.getArticle(id)  .subscribe((data: any) => {   this.article = data;   console.log(this.article);   this.isLoadingResults = false;  }); }

Call that function when the component is initiated.

ngOnInit() {
  this.getArticleDetails(this.route.snapshot.params.id);
}

Add this function for delete article.

deleteArticle(id: any) {
  this.isLoadingResults = true;
  this.api.deleteArticle(id)
    .subscribe(res => {
        this.isLoadingResults = false;
        this.router.navigate(['/articles']);
      }, (err) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

For the view, open and edit src/app/article-detail/article-detail.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    list
  
  
    
      ## {{article.title}}

      {{article.author}} | {{article.updatedAt}}
    
    
      ### {{article.description}}

      
{{article.content}}

    
    
      edit
      delete
    
  

Finally, open and edit src/app/article-detail/article-detail.component.scss then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

Add an Article using Angular 8 Material

To create a form for adding an Article, open and edit src/app/add-article/add-article.component.ts then add these imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Inject above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

articleForm: FormGroup;
title = '';
author = '';
description = '';
content = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Next, create a class for MyErrorStateMatcher before the the main class @Components.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Add initial validation for each field.

ngOnInit() {
  this.articleForm = this.formBuilder.group({
    'title' : [null, Validators.required],
    'author' : [null, Validators.required],
    'description' : [null, Validators.required],
    'content' : [null, Validators.required]
  });
}

Create a function for submitting or POST article form.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.addArticle(this.articleForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/show-article', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      });
}

Next, open and edit src/app/add-article/add-article.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    list
  
  
    
      
        <input matInput placeholder="Title" formControlName="title"
               [errorStateMatcher]="matcher">
        
          Please enter Title
        
      
      
        <input matInput placeholder="Author" formControlName="author"
               [errorStateMatcher]="matcher">
        
          Please enter Author
        
      
      
        <input matInput placeholder="Description" formControlName="description"
               [errorStateMatcher]="matcher">
        
          Please enter Description
        
      
      
        <textarea matInput placeholder="Content" formControlName="content"
               [errorStateMatcher]="matcher">
        
          Please enter Content
        
      
      
        save
      
    
  

Finally, open and edit src/app/article-add/article-add.component.scss then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Edit an Article using Angular 8 Material

We have put an edit button inside the Article Detail component for call Edit page. Now, open and edit src/app/edit-article/edit-article.component.ts then add these imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Next, create a class for ErrorStateMatcher before the main class @Compoents.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Inject the imported modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the articles form before the constructor.

articleForm: FormGroup;
_id = '';
title = '';
author = '';
description = '';
content = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Next, add validation for all fields when the component is initiated.

ngOnInit() {
  this.getArticle(this.route.snapshot.params.id);
  this.articleForm = this.formBuilder.group({
    'title' : [null, Validators.required],
    'author' : [null, Validators.required],
    'description' : [null, Validators.required],
    'content' : [null, Validators.required]
  });
}

Create a function for getting article data that filled to each form fields.

getArticle(id: any) {
  this.api.getArticle(id).subscribe((data: any) => {
    this._id = data._id;
    this.articleForm.setValue({
      title: data.title,
      author: data.author,
      description: data.description,
      content: data.content
    });
  });
}

Create a function to update the article changes.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.updateArticle(this._id, this.articleForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/show-article', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

Add a function for handling the show article details button.

articleDetails() {
  this.router.navigate(['/show-article', this._id]);
}

Next, open and edit src/app/edit-article/edit-article.component.html then replace all HTML tags with this.


  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    
  
  
    info
  
  
    
      
        <input matInput placeholder="Title" formControlName="title"
               [errorStateMatcher]="matcher">
        
          Please enter Title
        
      
      
        <input matInput placeholder="Author" formControlName="author"
               [errorStateMatcher]="matcher">
        
          Please enter Author
        
      
      
        <input matInput placeholder="Description" formControlName="description"
               [errorStateMatcher]="matcher">
        
          Please enter Description
        
      
      
        <textarea matInput placeholder="Content" formControlName="content"
               [errorStateMatcher]="matcher">
        
          Please enter Content
        
      
      
        save
      
    
  

Finally, open and edit src/app/edit-article/edit-article.component.scss then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Integrate, Test and Run the Complete Nestjs, Fastify, MongoDB and Angular 8 Web Apps

To integrate the Angular 8 application into the Nestjs/Fastify application, open and edit src/main.js then add this import.

import { join } from 'path';

Add this line before the port listener line.

app.useStaticAssets({
  root: join(__dirname, '..', 'client/dist/client'),
  prefix: '/',
});

Next, make sure the MongoDB server is running then build the Angular 8 application.

cd client
ng build --prod

The Angular 8 build should be located in client/dist/client folder. Next, back to the root of Nestjs folder then run again the Nestjs application.

npm start

Open the browser and go to [http://localhost:3000](http://localhost:3000`) and you should see this application.

That it’s, the complete step by step tutorial of building a web app using Nestjs, Fastify, MongoDB and Angular 8. You can find the full source code in our GitHub.

#angular #mongodb #node-js #web-development