Ionic 5 Angular CRUD App with Node, Express and MongoDB

In this example, we will understand how to build CRUD operations in Ionic 5 Angular mobile application using Node.js, Express.js, and MongoDB.

We are creating an Ionic user management system to create, read, update, and delete user data. But before that, let us quickly understand Node, Express, and MongoDB that we are using in this tutorial to create the backend for handling CRUD operations.

Node is a popular open-source JavaScript runtime environment built on a V8 engine that directly invokes JavaScript code in a computer rather than the browser. You can profoundly write server-side applications and get easy access to the operating system, file system, and everything else needed to develop fully-operative applications.

Express is also an open-source web application framework that works in the Node ecosystem and is essentially used to create impeccable APIs.

MongoDB is a popular NoSQL database, and data is accumulated in a particular document inside a collection. It is different than the conventional rows and columns mechanism.

Build CRUD Operations in Ionic 5 using Node and Express

We will use Node and express to create REST API and MongoDB to store the users data in our native ionic CRUD app and here are the instruction that we need to follow:

  • Step 1: Install Ionic Project
  • Step 2: Create Pages
  • Step 3: Create Routes and Navigation
  • Step 4: Create Ionic CRUD REST APIs with Node & Express
  • Step 5: Import HttpClientModule in App Module
  • Step 6: Create Service
  • Step 7: Implement Create
  • Step 8: Integrate Read and Delete
  • Step 9: Implement Update
  • Step 10: Test Ionic Native CRUD App

Install Ionic Project

The first and foremost step is to install Ionic CLI, execute the command to commence the installation:

npm install -g @ionic/cli

You can eventually install the new Ionic Angular blank app:

ionic start ionic-node-express-crud blank --type=angular

Move to the project directory:

cd ionic-node-express-crud

Create Pages

In this step we have to delete the default Home page and execute command to create new pages for user crud management app.

ng generate page create

ng generate page list

ng generate page update

Create Routes and Navigation

In the app routing module, we need to add path, redirectTo, and pathMatch routing parameters so that we can open the create user page when the app opens up.

Update app-routing.ts file:

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: '', 
    redirectTo: 'create', 
    pathMatch: 'full'
  },
  {
    path: 'create',
    loadChildren: () => import('./create/create.module').then( m => m.CreatePageModule)
  },
  {
    path: 'list',
    loadChildren: () => import('./list/list.module').then( m => m.ListPageModule)
  },
  {
    path: 'update/:id',
    loadChildren: () => import('./update/update.module').then( m => m.UpdatePageModule)
  },
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})

export class AppRoutingModule { }

In the subsequent step, add the ionic tabs and add routes or url inside the routerLink property to enable the navigation in the app.

Update app.component.html file:

<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>

<!-- Add navigation -->
<ion-tabs>
  <ion-tab-bar slot="bottom">
    <ion-tab-button routerLinkActive="tab-selected" routerLink="/list" tab="list">
      <ion-icon name="list-outline"></ion-icon>
      <ion-label>Users List</ion-label>
    </ion-tab-button>

    <ion-tab-button routerLinkActive="tab-selected" routerLink="/create" tab="create">
      <ion-icon name="person-outline"></ion-icon>
      <ion-label>Add User</ion-label>
    </ion-tab-button>
  </ion-tab-bar>
</ion-tabs>

Create Ionic CRUD REST APIs with Node & Express

In this step, we will learn how to create CRUD REST APIs using Node and Express; we need to create a specific project. Therefore, move to the Ionic project root,

Create a server folder:

mkdir server && cd server

The following command helps you create an empty npm project without transpiring through an interactive process; additionally, -y stands for yes.

So, evoke the suggested command from the terminal:

npm init -y

We need to install the mongoose, body-parser, express, and cors packages to create a backend server application.

npm install mongoose body-parser express cors

Make sure to install the nodemon package, its a unique tool that is used in node applications. It automatically restarts the node application if found any changes are made in files or directory.

npm install nodemon --save-dev

--save-dev appends the third-party plugin to the package’s development dependencies.

To store the mobile crud app data, we are using MongoDB, so create db folder and create database.js file.

Update the given below code in the db/database.js file:

module.exports = {
  db: 'mongodb://localhost:27017/mongodb'
};

Let us define the model which represents the skeleton structure of the User object in the database. Hence, create model and User.js file.

Update the suggested code in the model/User.js file:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

let User = new Schema({
  name: {
    type: String
  },
  email: {
    type: String
  },
  username: {
    type: String
  }
}, {
  collection: 'users'
})

module.exports = mongoose.model('User', User)

Furthermore, we need to set up routes, so we have to create, create, read, update and delete routes using express js. Now, you need to create a routes folder and create a user.route.js file, then add the given below code in the routes/user.route.js file.

const express = require('express');
const app = express();
const userRoute = express.Router();
let UserModel = require('../model/User');


userRoute.route('/').get((req, res) => {
  UserModel.find((error, user) => {
    if (error) {
      return next(error)
    } else {
      res.json(user)
      console.log('Users retrieved!')
    }
  })
})


userRoute.route('/create-user').post((req, res, next) => {
  UserModel.create(req.body, (err, user) => {
    if (err) {
      return next(err)
    } else {
      res.json(user)
      console.log('User created!')
    }
  })
});


userRoute.route('/fetch-user/:id').get((req, res) => {
  UserModel.findById(req.params.id, (err, user) => {
    if (err) {
      return next(err)
    } else {
      res.json(user)
      console.log('User retrieved!')
    }
  })
})


userRoute.route('/update-user/:id').put((req, res, next) => {
  UserModel.findByIdAndUpdate(req.params.id, {
    $set: req.body
  }, (err, user) => {
    if (err) {
      return next(err);
    } else {
      res.json(user)
      console.log('User updated!')
    }
  })
})

userRoute.route('/delete-user/:id').delete((req, res, next) => {
  UserModel.findByIdAndRemove(req.params.id, (error, user) => {
    if (error) {
      return next(error);
    } else {
      res.status(200).json({
        msg: user
      })
      console.log('User deleted!')
    }
  })
})

module.exports = userRoute;

Finally, we have to create an index.js file in the server root folder, and it manages routing, app startup, and other essential functions and methods of our node server app.

Update index.js file:

const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const database = require('./db/database');

// MongoDB connection 
mongoose.Promise = global.Promise;
mongoose.connect(database.db, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useFindAndModify: false
}).then(() => {
  console.log('Database connected ')
},
  error => {
    console.log('Database not connected : ' + error)
  }
)

const userRoute = require('./routes/user.route')

const app = express();
app.use(express.json());
app.use(express.urlencoded({
  extended: false
}));
app.use(cors());

app.use('/api', userRoute)

const port = process.env.PORT || 5000;

app.listen(port, () => {
  console.log('PORT connected: ' + port)
})

app.use(function (error, res,) {
  console.error(error.message);
  if (!error.statusCode) error.statusCode = 500;
  res.status(error.statusCode).send(error.message);
});

Now, in this step, you have to start the nodemon server, so open terminal and type command in the command prompt, and execute the command:

nodemon server

You may use the following command to start the mongoDB:

mongod

Once the node server is working fine then, you can check the data progress on the following url:

http://localhost:5000/api

Import HttpClientModule in App Module

You have to import the HttpClientModule from the @angular/common/http package, plus inject it inside the imports array in app.module.ts file.

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

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

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


@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, 
    IonicModule.forRoot(), 
    AppRoutingModule,
    HttpClientModule // injected module
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})

export class AppModule {}

Create Service

Let us create an angular service and create, read, update and delete reusable functions, and we can inject this service in any component to make the HTTP calls.

ng generate service services/userCrud

Update services/userCrud.service.ts file:

import { Injectable } from '@angular/core';

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

export class User {
  _id: number;
  name: string;
  email: string;
  username: string;
}

@Injectable({
  providedIn: 'root'
})

export class UserCrudService {

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

  constructor(private httpClient: HttpClient) { }

  createUser(user: User): Observable<any> {
    return this.httpClient.post<User>('http://localhost:5000/api/create-user', user, this.httpOptions)
      .pipe(
        catchError(this.handleError<User>('Error occured'))
      );
  }

  getUser(id): Observable<User[]> {
    return this.httpClient.get<User[]>('http://localhost:5000/api/fetch-user/' + id)
      .pipe(
        tap(_ => console.log(`User fetched: ${id}`)),
        catchError(this.handleError<User[]>(`Get user id=${id}`))
      );
  }

  getUsers(): Observable<User[]> {
    return this.httpClient.get<User[]>('http://localhost:5000/api')
      .pipe(
        tap(users => console.log('Users retrieved!')),
        catchError(this.handleError<User[]>('Get user', []))
      );
  }

  updateUser(id, user: User): Observable<any> {
    return this.httpClient.put('http://localhost:5000/api/update-user/' + id, user, this.httpOptions)
      .pipe(
        tap(_ => console.log(`User updated: ${id}`)),
        catchError(this.handleError<User[]>('Update user'))
      );
  }

  deleteUser(id): Observable<User[]> {
    return this.httpClient.delete<User[]>('http://localhost:5000/api/delete-user/' + id, this.httpOptions)
      .pipe(
        tap(_ => console.log(`User deleted: ${id}`)),
        catchError(this.handleError<User[]>('Delete user'))
      );
  }


  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(error);
      console.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }  
  
}

Implement Create

We need to use UserCrudService to access the createUser() method and can make the POST request to create the user data object into the MongoDB database. Ideally, we also use the Form modules to create and get the user entered values to generate the user data.

Add create.page.ts file:

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

import { Router } from '@angular/router';
import { FormGroup, FormBuilder } from "@angular/forms";
import { UserCrudService } from './../services/user-crud.service';

@Component({
  selector: 'app-create',
  templateUrl: './create.page.html',
  styleUrls: ['./create.page.scss'],
})

export class CreatePage implements OnInit {

  userForm: FormGroup;

  constructor(
    private router: Router,
    public formBuilder: FormBuilder,
    private zone: NgZone,
    private userCrudService: UserCrudService    
  ) {
    this.userForm = this.formBuilder.group({
      name: [''],
      email: [''],
      username: ['']
    })
  }

  ngOnInit() { }

  onSubmit() {
    if (!this.userForm.valid) {
      return false;
    } else {
      this.userCrudService.createUser(this.userForm.value)
        .subscribe((response) => {
          this.zone.run(() => {
            this.userForm.reset();
            this.router.navigate(['/list']);
          })
        });
    }
  }

}

Create the basic form with name, email, and username value with form control name property.

Update create.page.html file:

<ion-header>
  <ion-toolbar>
    <ion-title>Create User</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list lines="full">
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <ion-item>
        <ion-label position="floating">Name</ion-label>
        <ion-input formControlName="name" type="text" required></ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">Email</ion-label>
        <ion-input formControlName="email" type="text" required>
        </ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">User name</ion-label>
        <ion-input formControlName="username" type="text" required>
        </ion-input>
      </ion-item>

      <ion-row>
        <ion-col>
          <ion-button type="submit" color="danger" expand="block">Add</ion-button>
        </ion-col>
      </ion-row>
    </form>
  </ion-list>
</ion-content>

Next, you have to add ReactiveFormsModule in the create.module.ts file, just right beside the FormsModule; this allows you to work with the angular forms.

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

@NgModule({
  imports: [
    ReactiveFormsModule
  ]
})

Integrate Read and Delete

In this step, we will retrieve the users’ list from the mongo database and show it in the ionic page view using the ngFor directive. Also, we will show you how to quickly delete the user object from the database using the deleteUser() method.

Add list.page.ts file:

import { Component, OnInit } from '@angular/core';
import { UserCrudService } from './../services/user-crud.service';

@Component({
  selector: 'app-list',
  templateUrl: './list.page.html',
  styleUrls: ['./list.page.scss'],
})

export class ListPage implements OnInit {

  Users: any = [];

  constructor( private userCrudService: UserCrudService ) { }

  ngOnInit() { }

  ionViewDidEnter() {
    this.userCrudService.getUsers().subscribe((response) => {
      this.Users = response;
    })
  }

  removeUser(user, i) {
    if (window.confirm('Are you sure')) {
      this.userCrudService.deleteUser(user._id)
      .subscribe(() => {
          this.Users.splice(i, 1);
          console.log('User deleted!')
        }
      )
    }
  }

}

Update list.page.html file:

<ion-header>
  <ion-toolbar>
    <ion-title>Users List</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

  <ion-list>
    <ion-item *ngFor="let user of Users">
      <ion-label>
        <h2>{{user.name}}</h2>
        <h3>{{user.email}}</h3>
        <h3>{{user.username}}</h3>
      </ion-label>

      <div class="item-note" item-end>
        <button ion-button clear [routerLink]="['/update/', user._id]">
          <ion-icon name="create" style="zoom:1.5"></ion-icon>
        </button>
        <button ion-button clear (click)="removeUser(user, i)">
          <ion-icon name="trash" style="zoom:1.5"></ion-icon>
        </button>
      </div>
    </ion-item>
  </ion-list>

</ion-content>

Implement Update

In the last segment, we have to update the user object; the getUser() method requires the user id, we may get it using the ActivatedRoute. Set the user values at the initial load and update when clicked on the update button.

So, first you have to open the update.module.ts file and add the ReactiveFormsModule.

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

@NgModule({
  imports: [
    ReactiveFormsModule
  ]
})

Add update.page.ts file:

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

import { Router, ActivatedRoute } from "@angular/router";
import { FormGroup, FormBuilder } from "@angular/forms";
import { UserCrudService } from './../services/user-crud.service';


@Component({
  selector: 'app-update',
  templateUrl: './update.page.html',
  styleUrls: ['./update.page.scss'],
})

export class UpdatePage implements OnInit {

  updateUserFg: FormGroup;
  id: any;

  constructor(
    private userCrudService: UserCrudService,
    private activatedRoute: ActivatedRoute,
    public formBuilder: FormBuilder,
    private router: Router
  ) {
    this.id = this.activatedRoute.snapshot.paramMap.get('id');
  }

  ngOnInit() {
    this.fetchUser(this.id);
    this.updateUserFg = this.formBuilder.group({
      name: [''],
      email: [''],
      username: ['']
    })
  }

  fetchUser(id) {
    this.userCrudService.getUser(id).subscribe((data) => {
      this.updateUserFg.setValue({
        name: data['name'],
        email: data['email'],
        username: data['username']
      });
    });
  }

  onSubmit() {
    if (!this.updateUserFg.valid) {
      return false;
    } else {
      this.userCrudService.updateUser(this.id, this.updateUserFg.value)
        .subscribe(() => {
          this.updateUserFg.reset();
          this.router.navigate(['/list']);
        })
    }
  }

}

Update update.page.html file:

<ion-header>
  <ion-toolbar>
    <ion-title>Update User</ion-title>
  </ion-toolbar>
</ion-header>


<ion-content>
  <ion-list lines="full">
    <form [formGroup]="updateUserFg" (ngSubmit)="onSubmit()">
      
      <ion-item>
        <ion-label position="floating">Name</ion-label>
        <ion-input formControlName="name" type="text" required></ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">Email</ion-label>
        <ion-input formControlName="email" type="text" required>
        </ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">User name</ion-label>
        <ion-input formControlName="username" type="text" required>
        </ion-input>
      </ion-item>      

      <ion-row>
        <ion-col>
          <ion-button type="submit" color="success" expand="block">Update</ion-button>
        </ion-col>
      </ion-row>
      
    </form>
  </ion-list>
</ion-content>

Test Ionic Native CRUD App

We have developed the ionic native crud app, so head over to the command prompt and follow the given instructions:

Add the required platform:

# iOS
ionic cordova platform add ios

# Android
ionic cordova platform add android

Next, create the runnable build:

# iOS
ionic cordova build ios

# Android
ionic cordova build android

Finally, start the app on the device:

# iOS
ionic cordova run ios -l

# Android
ionic cordova run android -l

Conclusion

Ionic 5 Native Crud App is completed; throughout this extensive guide, we discussed how to create express crud REST APIs, setup an ionic app from scratch. Also, looked at how to create a node backend to handle crud operations and store the crud data in the MongoDB database in Ionic, angular application.

We hope you liked this tutorial, and you may download the code of this tutorial from GitHub.