Angular Material Reactive Forms & Node.js API CRUD

Table of Contents
Overview
This article is an overview of a simple CRUD application to manage users. The application is built with Angular Reactive Forms and Node.js for the API. The example application will show a text fields for name, email, description, and a material drop down list for categories. The edit user form will get the record data and use FormBuilder and FormControl to fill in and gather the form data for submission. The default add user form will also use reactive forms to make sure the required fields are filled out. Node.js API works with a SQLite3 database. For a more in-depth article on how to build a Node.js API with SQLite3 please check out on of the following:

Source Files for the Example
Recommend cloning or downloading the working example just in case something is missing in the article.
Get Source FilesHow to run the example

For Angular
cd ng-demp-app
ng s -o
For Node.js
cd API
npm start
Create a New Angular Application
First, create a new application
ng new my-app
Once the application has been created change directory into the new application folder.
cd my-app
Install Material with Angular Schematics – ‘ng add’. This will take care of installing Material and it dependencies and basic configuration. You will still need to take care of the imports which is discussed in the next section.
ng add @angular/material
Create the following components
ng g c components/user-parent-layout
ng g c components/add-user-form
ng g c components/edit-user-form
ng g s services/user
ng g class models/user-class
In the app.module.html remove the default generated content and just place the parent-layout-component.
<app-users-parent-layout></app-users-parent-layout>
Material Module
Because there are a lot of imports to using Material, most examples I have seen use a separate module to handle these imports. Below is a step by step on creating the module and adding it to the root module app.module.ts.
Create a new module labeled material.module.ts. Link to article on how to do this.
Make sure you have the following imports in your apps.module.ts file.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientJsonpModule, HttpClientModule } from '@angular/common/http';
import { MaterialModule } from './material.module';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule,
BrowserAnimationsModule,
HttpClientModule,
HttpClientJsonpModule,
BrowserAnimationsModule,
FormsModule,
MaterialModule
],
providers: [],
entryComponents: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Add the imports needed to the Root app.module.ts or Feature Module.
Code snippet from app.module.ts
import { MaterialModule } from './material.module';
....
@NgModule({
declarations: [],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
HttpClientModule,
HttpClientJsonpModule,
BrowserAnimationsModule,
ReactiveFormsModule,
FormsModule,
MaterialModule
....
User Class
Create a user class file user-class.ts for edit form to submit both the form builder and control data to the users.service.ts.

Typescript
Code for add-user-form.component.ts
export class UserClass {
Id: string = '';
Name: string = '';
Description: string = '';
Email: string = '';
Category: string = '';
}
User Services
The Angular Service file users.service.ts manages the API calls to node.js.
Typescript
Code for add-user-form.component.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { UserClass } from '../models/user-class';
@Injectable({
providedIn: 'root'
})
export class UsersService {
constructor(private http: HttpClient) { }
getAllUsers(): Observable<any> {
const URL = `http://localhost:3004/api/users`;
return this.http.get<any>(URL);
}
getSingleUser(id: number): Observable<any> {
const URL = `http://localhost:3004/api/user/${id}`;
return this.http.get<any>(URL);
}
updateSingleUser(data: UserClass): Observable<any> {
const URL = `http://localhost:3004/api/user`;
const throttleConfig = {
leading: false,
trailing: true
}
const body = new HttpParams()
.set('Id', data.Id.toString())
.set('Name', data.Name)
.set('Email', data.Email)
.set('Description', data.Description)
.set('Category', data.Category.toString());
console.log('body', body.toString())
return this.http.put<any>(URL, body);
}
addSingleUser(data: UserClass): Observable<any> {
const URL = `http://localhost:3004/api/user`;
const body = new HttpParams()
.set('Name', data.Name)
.set('Email', data.Email)
.set('Description', data.Description)
.set('Category', data.Category.toString());
return this.http.post<any>(URL, body);
}
deleteSingleUser(id: number): Observable<any> {
const URL = `http://localhost:3004/api/user/${id}`;
return this.http.delete<any>(URL);
}
}
User Add Form Component
The form component has three Angular Material Text and one Select field controls. Uses form builder to validate input for the text fields and a form control for the select field. Once all the fields have been validated a signature field is enabled which then makes the Submit button available.
Typescript
Code for add-user-form.component.ts
import { Component, OnInit, OnDestroy, EventEmitter, Output, Input } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { UsersService } from '../../services/users.service';
import { FormBuilder, Validators, FormControl, FormGroup } from '@angular/forms';
import { FormGroupDirective, NgForm} from '@angular/forms';
import { Subscription } from 'rxjs';
import { IUsers, IWebsiteType } from 'src/app/interfaces/iusers';
import { HttpErrorResponse } from '@angular/common/http';
import { UserClass } from 'src/app/models/user-class';
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));
}
}
@Component({
selector: 'app-add-user-form',
templateUrl: './add-user-form.component.html',
styleUrls: ['./add-user-form.component.scss']
})
export class AddUserFormComponent implements OnInit, OnDestroy {
public data: any;
@Input() pageDetails: any;
@Input() tableData: IUsers[] = [];
@Output() showMessage = new EventEmitter<boolean>();
@Output() addNewRecord = new EventEmitter<boolean>();
private subs = new Subscription();
private newUserCls = new UserClass();
public emailFormControl = new FormControl('', [Validators.required, Validators.email]);
public nameArray: any;
public overlapPageArray: any;
public toNameArray: any;
public fromNameArray: any;
public eventForm = new FormGroup({})
public isActive = new FormControl('', [Validators.required]);
public appCategory = new FormControl('', [Validators.required]);
appTypes: IWebsiteType[] =
[{ value: "1", viewValue: "Singer" },
{ value: "2", viewValue: "Bassist" },
{ value: "3", viewValue: "Guitarist" },
{ value: "4", viewValue: "Drummer" }];
constructor(private fb: FormBuilder, private userSVC: UsersService) { }
ngOnInit() {
this.toNameArray = [];
this.fromNameArray = [];
this.showMessage.emit(false);
this.eventForm = this.fb.group({
id: [null],
name: [null, [Validators.required, Validators.minLength(2), Validators.maxLength(80)]],
email: [null, [Validators.required, Validators.email]],
description: [null, [Validators.required, Validators.minLength(2), Validators.maxLength(200)]],
});
this.appCategory = new FormControl(0);
}
ngOnDestroy() {
if (this.subs) {
this.subs.unsubscribe();
}
}
//* Get Application ID for Drop Down / Form Control
getApplicationID(value: any): number {
var appVal = 0;
var counter = 0;
this.appTypes.forEach(item => {
if (item.value == value) {
appVal = counter;
}
counter++;
})
return appVal;
}
//* Submit Form to Parent Layout Component
onSubmit($event: any) {
this.addNewRecord.emit(true)
this.newUserCls.Id = this.eventForm.controls.id.value;
this.newUserCls.Name = this.eventForm.controls.name.value;
this.newUserCls.Email = this.eventForm.controls.email.value;
this.newUserCls.Description = this.eventForm.controls.description.value;
this.newUserCls.Category = this.appCategory.value;
this.subs.add(this.userSVC.addSingleUser(this.newUserCls).subscribe((response) => {
// console.log(response);
this.addNewRecord.emit(true);
},
(err: HttpErrorResponse) => {
console.log(err);
}));
}
}
HTML
The following source is the html markup.
<form [formGroup]="eventForm" novalidate (ngSubmit)="onSubmit($event)" autocomplete="off" class="example-form">
<table class="example-full-width" cellspacing="0"><tr>
<td>
<mat-form-field class="example-full-width" appearance="fill" >
<mat-label>Name</mat-label>
<input matInput formControlName="name">
<mat-error *ngIf="eventForm.controls.name.touched && eventForm.controls.name.invalid">
<span *ngIf="eventForm.controls.name.errors">Invalid Title (2 to 80 Characters)</span>
</mat-error>
</mat-form-field>
</td>
<td>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Email</mat-label>
<input matInput formControlName="email" required>
<mat-error *ngIf="eventForm.controls.email.touched && eventForm.controls.email.invalid">
<span >Invalid Email</span>
</mat-error>
</mat-form-field>
</td>
</tr>
</table>
<p>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Description</mat-label>
<textarea matInput placeholder="" #description formControlName="description"></textarea>
<mat-hint >{{description.value.length}}/200</mat-hint>
</mat-form-field>
</p>
<table class="example-full-width" cellspacing="0"><tr>
<td>
<div class="col-3 text-left">
<mat-form-field appearance="fill">
<mat-label>Page Type</mat-label>
<mat-select [formControl]="appCategory">
<mat-option>Please Select</mat-option>
<mat-option *ngFor="let option of appTypes" [value]="option.value" >
{{option.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</td>
</tr>
</table>
<mat-form-field class="example-full-width" appearance="fill" >
<mat-label>Signature</mat-label>
<input matInput value="" [disabled]="eventForm.invalid" #signature>
</mat-form-field>
<mat-divider class="mt-5"></mat-divider>
<div class="mt-5">
<button mat-raised-button color="primary"
[disabled]="signature.value.length < 3"
type="submit">Submit </button>
</div>
</form>
User Edit Form Component
The form component has 3 Angular Material Text and Select field controls. Uses form builder to validate input for the 3 fields and the form control. The parent component calls the openRecord() function that is passed an id from the users table. The form control is looked up by a hard coded JSON object IWebsiteType[]. Ideally, the values for the select field would come from an API call. Since the 4 fields are already validated just a signature is needed to enabled the Save button.
Typescript
Code for edit-user-form.component.ts
import { Component, OnInit, OnDestroy, EventEmitter, Output, Input } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { UsersService } from '../../services/users.service';
import { FormBuilder, Validators, FormControl, FormGroup } from '@angular/forms';
import { FormGroupDirective, NgForm} from '@angular/forms';
import { Subscription } from 'rxjs';
import { IUsers, IWebsiteType } from 'src/app/interfaces/iusers';
import { HttpErrorResponse } from '@angular/common/http';
import { UserClass } from 'src/app/models/user-class';
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));
}
}
@Component({
selector: 'app-edit-user-form',
templateUrl: './edit-user-form.component.html',
styleUrls: ['./edit-user-form.component.scss']
})
export class EditUserFormComponent implements OnInit, OnDestroy {
public data: any;
@Input() pageDetails: any;
@Input() tableData: IUsers[] = [];
@Output() showMessage = new EventEmitter<boolean>();
@Output() saveChangeToRecord = new EventEmitter<boolean>();
private subs = new Subscription();
private newUserCls = new UserClass();
public emailFormControl = new FormControl('', [Validators.required, Validators.email]);
public toNameArray: any;
public fromNameArray: any;
public eventForm = new FormGroup({})
public isActive = new FormControl('', [Validators.required]);
public appCategory = new FormControl('', [Validators.required]);
appTypes: IWebsiteType[] =
[{ value: "1", viewValue: "Singer" },
{ value: "2", viewValue: "Bassist" },
{ value: "3", viewValue: "Guitarist" },
{ value: "4", viewValue: "Drummer" }];
constructor(private fb: FormBuilder, private userSVC: UsersService) { }
// I N I T
ngOnInit() {
this.toNameArray = [];
this.fromNameArray = [];
this.showMessage.emit(false);
this.eventForm = this.fb.group({
id: [null],
name: [null, [Validators.required, Validators.minLength(2), Validators.maxLength(80)]],
email: [null, [Validators.required, Validators.email]],
description: [null, [Validators.required, Validators.minLength(2), Validators.maxLength(200)]],
});
this.appCategory = new FormControl(0);
}
// D E S T R O Y
ngOnDestroy() {
if (this.subs) {
this.subs.unsubscribe();
}
}
//* Get Application ID for Drop Down / Form Control
getApplicationID(value: any): number {
var appVal = 0;
var counter = 0;
this.appTypes.forEach(item => {
if (item.value == value) {
appVal = counter;
}
counter++;
})
return appVal;
}
//* Open record called from the Parent Layout Component
openRecord(id: any): void {
//* This is where you would use a service call to get a single record
this.subs.add(this.userSVC.getSingleUser(id).subscribe((response) => {
console.log(response)
var record = response.data[0];
this.eventForm = this.fb.group({
id: [record.Id],
name: [record.Name, [Validators.required, Validators.minLength(2), Validators.maxLength(80)]],
email: [record.Email, [Validators.required, Validators.email]],
description: [record.Description, [Validators.required, Validators.minLength(2), Validators.maxLength(200)]],
});
this.appCategory = new FormControl(this.appTypes[this.getApplicationID(record.Category)].value);
},
(err: HttpErrorResponse) => {
console.log(err);
}));
}
//* Submit Form to Parent Layout Component
onSubmit($event: any) {
this.newUserCls.Id = this.eventForm.controls.id.value;
this.newUserCls.Name = this.eventForm.controls.name.value;
this.newUserCls.Email = this.eventForm.controls.email.value;
this.newUserCls.Description = this.eventForm.controls.description.value;
this.newUserCls.Category = this.appCategory.value;
console.log(this.newUserCls)
this.subs.add(this.userSVC.updateSingleUser(this.newUserCls).subscribe((response) => {
console.log(response)
this.saveChangeToRecord.emit(true)
},
(err: HttpErrorResponse) => {
console.log(err);
}));
}
}
HTML
Code for edit-user-form.component.html
<form [formGroup]="eventForm" novalidate (ngSubmit)="onSubmit($event)" autocomplete="off" class="example-form">
<table class="example-full-width" cellspacing="0"><tr>
<td>
<mat-form-field class="example-full-width" appearance="fill" >
<mat-label>Name</mat-label>
<input matInput formControlName="name">
<mat-error *ngIf="eventForm.controls.name.touched && eventForm.controls.name.invalid">
<span *ngIf="eventForm.controls.name.errors">Invalid Title (2 to 80 Characters)</span>
</mat-error>
</mat-form-field>
</td>
<td>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Email</mat-label>
<input matInput formControlName="email" required>
<mat-error *ngIf="eventForm.controls.email.touched && eventForm.controls.email.invalid">
<span >Invalid Email</span>
</mat-error>
</mat-form-field>
</td>
</tr>
</table>
<p>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Description</mat-label>
<textarea matInput placeholder="" #description formControlName="description"></textarea>
<mat-hint >{{description.value.length}}/200</mat-hint>
</mat-form-field>
</p>
<table class="example-full-width" cellspacing="0"><tr>
<td>
<div class="col-3 text-left">
<mat-form-field appearance="fill">
<mat-label>Page Type</mat-label>
<mat-select [formControl]="appCategory">
<mat-option>Please Select</mat-option>
<mat-option *ngFor="let option of appTypes" [value]="option.value" >
{{option.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</td>
</tr>
</table>
<mat-form-field class="example-full-width" appearance="fill" >
<mat-label>Signature</mat-label>
<input matInput value="" [disabled]="eventForm.invalid" #signature>
</mat-form-field>
<mat-divider class="mt-5"></mat-divider>
<div class="mt-5">
<button mat-raised-button color="primary"
[disabled]="signature.value.length < 3"
type="submit">Submit </button>
</div>
</form>
Users Table Component
The Material Data Table loads initially with an API call to node.js. This gets the user record ID, Name, Email, Description, and Category. This is a plain table with no extra features enabled such as pagination or sorting to keep this example short. The table has actions on both ends of each row. Edit on the left side which fills in the Edit Form and a delete button on the right side to remove a single record. Every time a record is updated or removed a snackbar will appear at the top of the page with a message that closes after 3 seconds.
Typescript
Code for users-table.component.ts
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, OnInit, Input, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import { UsersService } from 'src/app/services/users.service';
@Component({
selector: 'app-users-table',
templateUrl: './users-table.component.html',
styleUrls: ['./users-table.component.scss']
})
export class UsersTableComponent implements OnInit {
@Input() tableData: any;
@Output() editRecord = new EventEmitter<number>();
@Output() deleteRecord = new EventEmitter<boolean>();
private subs = new Subscription();
public dataSource: any;
displayedColumns: string[] = ['Id', 'Name', 'Email', 'Description', 'Category', 'Action'];
constructor(private userSVC: UsersService) { }
//* Initialize
ngOnInit(): void {
this.dataSource = this.tableData;
}
//* Emits to Parent Layout Component that a record has been chosen
public onEditForm(id: any) {
// console.log(id);
this.editRecord.emit(id);
}
//* Called on by Parent Layout Component when the User Form Component submits a change
public onLoadData() {
this.subs.add(this.userSVC.getAllUsers().subscribe((response) => {
this.dataSource = response.data;
},
(err: HttpErrorResponse) => {
console.log(err);
}));
}
//* Delete a single record
public onDelete(id: any) {
this.subs.add(this.userSVC.deleteSingleUser(id).subscribe((response) => {
console.log(response);
this.deleteRecord.emit(true);
},
(err: HttpErrorResponse) => {
console.log(err);
}));
}
}
HTML
Using mat-table source from https://material.angular.io/components/table/overview.
Code for users-table.component.html
<div class="table">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- Position Column -->
<ng-container matColumnDef="Id">
<th mat-header-cell *matHeaderCellDef> ID </th>
<td mat-cell *matCellDef="let element"> <button mat-button (click)="onEditForm(element.Id)"> Edit</button> </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="Name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.Name}} </td>
</ng-container>
<!-- Email Column -->
<ng-container matColumnDef="Email">
<th mat-header-cell *matHeaderCellDef> Email </th>
<td mat-cell *matCellDef="let element"> {{element.Email}} </td>
</ng-container>
<!-- Description Column -->
<ng-container matColumnDef="Description">
<th mat-header-cell *matHeaderCellDef> Description </th>
<td mat-cell *matCellDef="let element"> {{element.Description}} </td>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="Category">
<th mat-header-cell *matHeaderCellDef> Category </th>
<td mat-cell *matCellDef="let element"> {{element.Category}} </td>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="Action">
<th mat-header-cell *matHeaderCellDef> Action </th>
<td mat-cell *matCellDef="let element">
<button mat-button color="warn" (click)="onDelete(element.Id)">
Delete
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
Users Parent Layout Component
The Parent Layout Component handles the communication between child components. I wrote it this way versus using a service because it is easier to understand the parent child component of emitting to the parent and the parent calling a function in the child component. An improvement would be to modify the record in the JSON Data instead of an API call to reload the records after an edit.
Important! There is a delay for the Parent to call the child component openRecord(id) function with the @ViewChild() decorator. This delay is necessary for the edit-user-form.component to render after hiding the add-user-form.component. If the openRecord(id) is called before the component is rendered then the record doesn’t get loaded since the parent cannot call a component that hasn’t rendered yet.

Alternative solution is to use a Behavior Subject from a service between the child components.
Typescript
Code for users-parent-layout.component.ts
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { EditUserFormComponent } from '../edit-user-form/edit-user-form.component';
import { UsersTableComponent } from '../users-table/users-table.component';
import { IWebsiteType } from 'src/app/interfaces/iusers';
import { Subscription } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { UsersService } from 'src/app/services/users.service';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-users-parent-layout',
templateUrl: './users-parent-layout.component.html',
styleUrls: ['./users-parent-layout.component.scss']
})
export class UsersParentLayoutComponent implements OnInit, OnDestroy {
@ViewChild(EditUserFormComponent, { static: false }) childFormComp!: EditUserFormComponent;
@ViewChild(UsersTableComponent, { static: false }) childTableComp!: UsersTableComponent;
private subs = new Subscription();
public toggleShowEditForm: boolean = false;
public tableData: any;
public isDataLoaded: boolean = false;
appTypes: IWebsiteType[] =
[{ value: "1", viewValue: "Singer" },
{ value: "2", viewValue: "Bassist" },
{ value: "3", viewValue: "Guitarist" },
{ value: "4", viewValue: "Drummer" }];
constructor(private userSVC: UsersService, private _snackBar: MatSnackBar) { }
//* Initialize
ngOnInit(): void {
this.isDataLoaded = false;
this.subs.add(this.userSVC.getAllUsers().subscribe((response) => {
console.log(response)
this.tableData = response.data;
this.isDataLoaded = true;
},
(err: HttpErrorResponse) => {
console.log(err);
}));
}
// D E S T R O Y
ngOnDestroy() {
if (this.subs) {
this.subs.unsubscribe();
}
}
//* Called by User Table Component to select a record and sends the record id to the Edit User Form Component
public onEditRecord(id: any): void {
//* Render the edit form and hide the add form
this.toggleShowEditForm = true;
//* Delay so the edit form can render first before calling the openRecord() function in the component
setTimeout(() => {
this.childFormComp.openRecord(id);
}, 300);
}
//* Called by User Form Component to save the changes and reload the User Table Component with the new data
public onSaveChangeToRecord(record: any) {
this.childTableComp.onLoadData();
this._snackBar.open('Record Saved', 'X', {
duration: 3000,
verticalPosition: 'top'
});
//* Hide the edit form and render the add form
this.toggleShowEditForm = false;
}
//* Called by Add User Form Component to save the changes and reload the User Table Component with the new data
public onAddNewRecord(record: any) {
this.childTableComp.onLoadData();
this._snackBar.open('New Record Added', 'X', {
duration: 3000,
verticalPosition: 'top'
});
}
//* Called by Add User Form Component to save the changes and reload the User Table Component with the new data
public onDeleteRecord($event: any) {
this.childTableComp.onLoadData();
this._snackBar.open('Record Deleted', 'X', {
duration: 3000,
verticalPosition: 'top'
});
}
}
HTML
Using <table mat-table source from https://material.angular.io/components/table/overview.
Code for users-parent-layout.component.html
<div class="grid-container">
<mat-grid-list cols="12" rowHeight="100px">
<mat-grid-tile [colspan]="6" [rowspan]="6">
<app-edit-user-form [tableData]="tableData" (saveChangeToRecord)="onSaveChangeToRecord($event)"
*ngIf="toggleShowEditForm">
</app-edit-user-form>
<app-add-user-form (addNewRecord)="onAddNewRecord($event) *ngIf="!toggleShowEditForm">
</app-add-user-form>
</mat-grid-tile>
<mat-grid-tile [colspan]="6" [rowspan]="6">
<app-users-table class="table" [tableData]="tableData" (deleteRecord)="onDeleteRecord($event)" (editRecord)="onEditRecord($event)" *ngIf="isDataLoaded">
</app-users-table>
</mat-grid-tile>
</mat-grid-list>
</div>
Node.js API
It’s about time meme
The node.js API uses the following libraries.
Below are the actual working version of each library
"cors": "^2.8.5",
"express": "^4.17.3",
"nodemon": "^2.0.15",
"sqlite3": "^5.0.2"
npm install express, cors, nodemon, sqlite3
Almost Done!

The API
The API will first create a database labeled usersdb.sqlite if one does not exist. Then prefills the users table with 9 records. The API paths are all ‘api/user’ or some variation of it. Using Get, Put, Patch, Delete to differentiate the CRUD operations.
Code for app.js
const express = require('express');
const app = express();
const port = 3004;
var sqlite3 = require('sqlite3').verbose()
const cors = require('cors');
const DBSOURCE = "usersdb.sqlite";
let db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message)
throw err
}
else {
// ** EXAMPLE **
// ** For a column with unique values **
// email TEXT UNIQUE,
// with CONSTRAINT email_unique UNIQUE (email)
db.run(`CREATE TABLE Users (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT,
Email TEXT,
Description TEXT,
Category INTEGER,
DateModified DATE,
DateCreated DATE
)`,
(err) => {
if (err) {
// Table already created
} else {
// Table just created, creating some rows
var insert = 'INSERT INTO Users (Name, Description, Email, Category, DateCreated) VALUES (?,?,?,?,?)'
db.run(insert, ['Eddie Van Halen', 'Lead Guitar for Van Halen', 'eddiev@email.com', 3, Date('now')])
db.run(insert, ['Joe Satriani', 'Lead Guitar for Joe Satriani', 'joe@email.com', 3, Date('now')])
db.run(insert, ['Dave Mathews', 'Singer Guitarist for Dave Mathews', 'dave@email.com', 1, Date('now')])
db.run(insert, ['Sandy Saraya', 'Lead singer of Saraya', 'saraya@email.com', 1, Date('now')])
db.run(insert, ['Rosy Tedjedor', 'Singer of My FIrst Crush', 'rosy@email.com', 1, , Date('now')])
db.run(insert, ['Aaron Spears', 'Gospil Drumer', 'aaron@email.com', 3, Date('now')])
db.run(insert, ['Neil Piert', 'Drummer for Rush', 'neil@email.com', 4, Date('now')])
db.run(insert, ['Geddy Lee', 'Bassist for Rush', 'geddy@email.com', 2, Date('now')])
db.run(insert, ['Alex Lifeson', 'Lead Guitarist for Rush', 'alex@email.com', 3, Date('now')])
db.run(insert, ['Jeff Pacaro', 'Original Drumer for Totto', 'jeff@email.com', 4, Date('now')])
}
});
}
});
module.exports = db
app.use(
express.urlencoded(),
cors()
);
app.get('/', (req, res) => res.send('API Root'));
//* G E T A L L
app.get("/api/users", (req, res, next) => {
var sql = "SELECT * FROM Users"
var params = []
db.all(sql, params, (err, rows) => {
if (err) {
res.status(400).json({ "error": err.message });
return;
}
res.json({
"message": "success",
"data": rows
})
});
});
//* G E T S I N G L E P R O D U C T
app.get("/api/user/:id", (req, res, next) => {
var sql = "SELECT * FROM Users WHERE Id = ?"
db.all(sql, req.params.id, (err, rows) => {
if (err) {
res.status(400).json({ "error": err.message });
return;
}
res.json({
"message": "success",
"data": rows
})
});
})
//* C R E A T E
app.post("/api/user", (req, res) => {
var errors = []
console.log('req', req.body)
if (!req.body.Name) {
errors.push("Name is missing");
}
if (!req.body.Email) {
errors.push("Email is missing");
}
if (!req.body.Description) {
errors.push("Description is missing");
}
if (!req.body.Category) {
errors.push("Category is missing");
}
if (errors.length) {
res.status(400).json({ "error": errors.join(",") });
return;
}
var data = {
Name: req.body.Name,
Email: req.body.Email,
Description: req.body.Description,
Category: req.body.Category,
DateCreated: Date('now')
}
var sql = 'INSERT INTO Users (Name, Email, Description, Category, DateCreated) VALUES (?,?,?,?,?)'
var params = [data.Name, data.Email, data.Description, data.Category ,Date('now')]
db.run(sql, params, function (err, result) {
if (err) {
res.status(400).json({ "error": err.message })
return;
}
res.json({
"message": "success",
"data": data,
"id": this.lastID
})
});
})
//* U P D A T E
app.put("/api/user", (req, res, next) => {
var data = [req.body.Name, req.body.Email, req.body.Description, req.body.Category, Date('now'), req.body.Id];
let sql = `UPDATE Users SET
Name = ?,
Email = ?,
Description = ?,
Category = ?,
DateModified = ?
WHERE Id = ?`;
db.run(sql, data, function (err) {
if (err) {
return console.error(err.message);
}
console.log(`Row(s) updated: ${this.changes}`);
});
res.json({
message: "success",
data: data,
changes: this.changes
})
})
//* D E L E T E
app.delete("/api/user/:id", (req, res, next) => {
console.log('req', req)
db.run(
'DELETE FROM Users WHERE id = ?',
req.params.id,
function (err, result) {
if (err) {
res.status(400).json({ "error": res.message })
return;
}
res.json({ "message": "Deleted", changes: this.changes })
});
})
app.listen(port, () => console.log(`API listening on port ${port}!`));

You must be logged in to post a comment.