CRUD operations with GraphQL and NestJS


This is the 5th article of the NestJS tutorial series with GraphQL, PostGraphile, Angular, BullJS, and TypeORM with PostgreSQL. Please check previous articles,

  1. File uploading example using NestJS and Angular 11 - Part 1
  2. File uploading example using NestJS and Angular 11 - Part 2
  3. Use Bull with NestJS
  4. Introduction to PostGraphile
  5. GraphQL and PostGraphile with NestJS

In this article, I am going to discuss the Update and Delete functions. Let's begin edit and delete functions. I am using a very simple UI to demonstrate these functions. 


Change the view.component.html


I am going to add buttons for edit and delete functions in the table.


<div style="margin: 1%; padding: 2%;">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Car make</th>
<th scope="col">Car model</th>
<th scope="col">Year</th>
<th scope="col">Engine Number</th>
<th scope="col">Edit data</th>
<th scope="col">Delete data</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let vehicle of vehicles">
<td>{{vehicle.id}}</td>
<td>{{vehicle.make}}</td>
<td>{{vehicle.model}}</td>
<td>{{vehicle.year}}</td>
<td>{{vehicle.engineNumber}}</td>
<td><button [routerLink]="'/edit/' + vehicle.id" class="btn btn-primary">Edit</button></td>
<td><button (click)="deleteVehicle(vehicle.id!)" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
</div>


As you can see I added two buttons for edit and delete. Edit function will be handled in another component (edit component will be created later). Therefore, I added routerLink for the edit button. The delete function will be handled using a click event and the same component (view component) will be used. In both scenarios, I will be using vehicle.id to identify the specific vehicle.


Completes delete function


The delete function is very simple, so I am going to handle it in the view component. Let's modify the view.component.ts file and add deleteVehicle() function and the GraphQL query. As you can see in the above template file


export const GET_VEHICLES = gql`
query{
vehicles{
id
make
model
year
engineNumber
}
}`



deleteVehicle(id: number) {

if (confirm("Are you sure to delete item with ID " + id)) {

this.apollo.mutate<any>({
mutation: DELETE_VEHICLE, variables: { id }, fetchPolicy: 'no-cache'
}).subscribe(({ data }) => {
window.location.reload()
})
}
}




Refactor the app.module.ts file


I will use FormsModule and ReactiveFormsModule to create forms.


import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { UploadComponent } from './upload/upload.component';
import { NavbarComponent } from './navbar/navbar.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { EditComponent } from './vehicle/edit/edit.component';
import { ViewComponent } from './vehicle/view/view.component';
import { GraphQLModule } from './graphql.module';

@NgModule({
declarations: [
AppComponent,
HomeComponent,
UploadComponent,
NavbarComponent,
EditComponent,
ViewComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
ReactiveFormsModule,
GraphQLModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }




Create a new component for the edit function


Hit the below command in the terminal to add a new component to the edit function

ng g c vehicle/edit


I added some CSS styles to the edit form using the edit.component.css file.


html, body, .container-table {
height: 100%;
}
.container-table {
display: table;
border: 1px rgb(226, 230, 236) solid;
padding: 20px;
}
.vertical-center-row {
display: table-cell;
vertical-align: middle;
}



Add simple form to edit data in edit.componenet.html file


<div class="container container-table">
<div class="row vertical-center-row">
<div class="text-center col-md-4 col-md-offset-4">
<form id="editForm">
<div class="form-group row">
<label for="carMake" class="col-sm-2 col-form-label">
Car make
</label>
<div class="col-sm-10">
<input type="text" class="form-control"
id="carMake" name="carMake" [(ngModel)]="make">
</div>
</div>
<div class="form-group row">
<label for="carModel" class="col-sm-2 col-form-label">
Car model
</label>
<div class="col-sm-10">
<input type="text" class="form-control"
id="carModel" name="carModel" [(ngModel)]="model">
</div>
</div>
<div class="form-group row">
<label for="year" class="col-sm-2 col-form-label">
Year
</label>
<div class="col-sm-10">
<input type="text" class="form-control"
id="year" name="year" [(ngModel)]="year">
</div>
</div>

<div class="form-group row">
<button style='margin-right:16px' type="submit"
class="btn btn-primary" (click)="editVehicle()">
Save details
</button>
<button style='margin-right:16px' type="reset"
class="btn btn-primary">
Clear
</button>
</div>
</form>
</div>
</div>
</div>


Let's modify the view.component.ts file. 


import { Component, OnInit } from '@angular/core';
import { Apollo, gql, QueryRef } from 'apollo-angular';
import { ActivatedRoute } from '@angular/router';
import { Vehicle } from '../vehicle.entity';

export const GET_VEHICLE = gql`
query($id: Int!) {
vehicle(id: $id){
make
model
year
engineNumber
}
}`

export const UPDATE_VEHICLE = gql` mutation MyMutation(
$id: Int!,
$make:String!,
$model:String!,
$year:String!){
updateVehicle(
id: $id,
make: $make,
model: $model,
year: $year){make model year
}
}`

@Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.css']
})
export class EditComponent implements OnInit {

private query !: QueryRef<any, any>;
id: number;
make!: string;
model!: string;
year!: string;
vehicle!: Vehicle;
data: any;

constructor(private apollo: Apollo, private route: ActivatedRoute) {
this.id = +this.route.snapshot.params.id;
}

ngOnInit(): void {
this.getVehicle(this.id);
}

getVehicle(id: number) {
this.query = this.apollo.watchQuery({
query: GET_VEHICLE, variables: { id }, fetchPolicy: 'no-cache'
});

this.query.valueChanges.subscribe(({ data }) => {
this.data = data;
this.make = this.data.vehicle.make;
this.model = this.data.vehicle.model;
this.year = this.data.vehicle.year;
});
}

editVehicle() {
if (confirm("Are you sure to save?")) {
let id = this.id;
let make = this.make;
let model = this.model;
let year = this.year;

this.apollo.mutate<any>({
mutation: UPDATE_VEHICLE, variables: { id, make, model, year },
fetchPolicy: 'no-cache'
}).subscribe(({ data }) => {
if (data != null) {
alert("Data updated successfully !")
window.location.href = './vehicles';
}
})
}
}

}



This looks complex, but actually not. I removed comments in the code above, but I added all comments in the final code. After the import statements, I have added queries to get a vehicle and update a vehicle. Once we click the edit button in the view component, the relevant vehicle should be loaded in the edit form. To perform this I have created a GraphQL query to find the vehicle by id.

In the constructor, I get the id as a number. The getVehicle() function will be used to retrieve vehicle data.

The editVehicle() will be used to edit data. Angular two-way data binding is used to retrieve modified data from the form. 


Add a route to the edit component

Add following route information to the app-routing.module.ts file.

        {
        path: 'edit/:id',
        pathMatch: 'full',
        component: EditComponent
        },



Modify the NestJS application


Now we can move with the server application. I am going to add a query and a mutation.


@Query(() => Vehicle)
async vehicle(@Args({ name: 'id', type: () => Int }) id: number): Promise<Vehicle> {
const query = gql`query MyQuery {
vehicleById(id: ${id}) {
id
make
model
year
engineNumber
}
}`
return request('http://localhost:5000/graphql', query).then((data) => {
return data.vehicleById;
});
}



@Mutation(() => Vehicle)
async updateVehicle(
@Args({ name: 'id', type: () => Int }) id: number,
@Args({ name: 'make', type: () => String }) make: string,
@Args({ name: 'model', type: () => String }) model: string,
@Args({ name: 'year', type: () => String }) year: string) {

const mutation = gql`mutation MyMutation {
updateVehicleById(
input: {vehiclePatch:
                     {make: "${make}", model: "${model}", year: "${year}"}, id: ${id}}
) {
vehicle {
id
make
model
year
}}}`

return request('http://localhost:5000/graphql', mutation).then((data) => {
return data.updateVehicleById.vehicle;
});
}



@Mutation(() => Vehicle)
async deleteVehicle(@Args('id', { type: () => Int }) id: number) {
const mutation = gql`
mutation MyMutation {
deleteVehicleById(input: {id: ${id}}) {
vehicle {
make
model
year
}
}
}
`
return await request('http://localhost:5000/graphql', mutation).then((data) => {
return data.deleteVehicleById;
});
}




Now we are all done. We can run the application now. Do not forget to start the redis-server and the PostGraphile service. You can use PostGraphile as a library in NestJS. Please refer to the documentation.

The form will look like this.




Part 06: File export using Angular, GraphQL and NestJS




CRUD operations with GraphQL and NestJS CRUD operations with GraphQL and NestJS Reviewed by Ravi Yasas on 3:27 AM Rating: 5

No comments:

Powered by Blogger.