Ionic 7 Angular CRUD App with MongoDB / Express API Tutorial

Last Updated on by in Angular

In this tutorial, we will learn how to create an Ionic 7 Angular 16 CRUD (Create, Read, Update, Delete) mobile application using Express REST API, Node, NPM and MongoDB local database.

In this Ionic Angular CRUD operations tutorial, you will find out how to create a hybrid mobile app in Ionic Angular using Node Express REST API.

You will create a music application which allows users to add, edit or update song data in Ionic environment.

Before we begin, we should always remember that we must use the latest Ionic version to avoid compatibility problems.

How to Build an Ionic 7 Angular CRUD App using Express REST APIs

  • Step 1: Ionic Environment Setup
  • Step 2: Install Ionic Angular App
  • Step 3: Create Ionic Routes
  • Step 4: Create Express REST APIs
  • Step 5: Create Ionic Angular API Service
  • Step 6: Display Data List & Delete Data
  • Step 7: Add Data in Ionic
  • Step 8: Ionic Edit Data
  • Step 9: Test App in Browser

Tutorial Requirements

You need to be familiar with the following tools and frameworks to understand this tutorial.

  • Node 18+
  • Express 4+
  • MongoDB
  • Mongoose 7+
  • Ionic
  • Angular 16+

To set up Node.js on your system, you need to download the latest version of Node from here.

You can also follow this tutorial to install Node js on your system.

Create Ionic Angular Cordova Project

To create the new blank Ionic CRUD mobile app, we need to execute the following command from the terminal.

ionic start ionic-angular-crud-app blank --type=angular

Get inside the project folder.

cd ionic-angular-crud-app

Run the below command to add the native core package.

npm install @ionic-native/core --legacy-peer-deps

Disable Strict Type Errors

To avoid TypeScript compiling issues, we just need to open tsconfig.json file:

First, set below property under “compilerOptions” property.

"compilerOptions": {
    "strictPropertyInitialization": false,
    "skipLibCheck": true,
    "noPropertyAccessFromIndexSignature": false
    ...
}

Secondly, add given props under “angularCompilerOptions”.

"angularCompilerOptions": {
    "strictTemplates": false,
    ...
}

Add Ionic Angular Routes

In this step you have to add the routes to enable navigation in the Ionic Cordova app, hence first execute below commands to generate Ionic pages.

ng generate page add-song

ng generate page edit-song

We have created the pages to create routes, these pages have also been automatically registered in app/app-routing.module.ts file:

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

const routes: Routes = [
  {
    path: 'home',
    loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
  },
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full'
  },
  {
    path: 'add-song',
    loadChildren: () => import('./add-song/add-song.module').then( m => m.AddSongPageModule)
  },
  {
    path: 'edit-song/:id',
    loadChildren: () => import('./edit-song/edit-song.module').then( m => m.EditSongPageModule)
  },
];

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

export class AppRoutingModule { }

Create Express REST API for Ionic Cordova App

Now, you require to create a backend server to manage the data in Ionic CRUD mobile app.

Set up a backend server with the help of Node, Express and MongoDB to create REST APIs for Create, Read, Update and Delete songs data also for storing the data in the MongoDB database.

From the root of your application execute following commands to create a separate folder for creating a locus of REST APIs:

mkdir backend && cd backend

Create the specific package.json for the node/express server.

npm init -y

Install following NPM packages.

npm install body-parser cors express mongoose http-errors

Install nodemon npm package as a development dependency to avoid re-starting the server every time we make the changes in the server files.

npm install nodemon --save-dev

Create model folder and also create Song.js file in the backend folder’s root then add the following code in it.

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

let Song = new Schema(
  {
    song_name: {
      type: String,
    },
    artist: {
      type: String,
    },
  },
  {
    collection: "songs",
  }
);

module.exports = mongoose.model("Song", Song);

Next, create REST APIs for creating, reading, updating and deleting song data for our Ionic CRUD mobile app. We will take help of express js middlewares, and later we will learn how to consume REST APIs in Ionic4/Angular app.

Create routes folder and also create song.route.js file in the backend folder’s root then add the following code in it.

const express = require("express");
const SongRoute = express.Router();
const SongModel = require("../model/Song");
const cors = require("cors");

// CORS OPTIONS
var whitelist = ["http://localhost:8100", "http://localhost:4000"];
var corsOptionsDelegate = function (req, callback) {
  var corsOptions;
  if (whitelist.indexOf(req.header("Origin")) !== -1) {
    corsOptions = {
      origin: "*",
      methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
    };
  } else {
    corsOptions = { origin: false }; // disable CORS for this request
  }
  callback(null, corsOptions);
};

// CREATE
SongRoute.route("/create-song").post(async (req, res, next) => {
  await SongModel.create(req.body)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully added!",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

// GET SINGLE
SongRoute.route("/get-song/:id").get(async (req, res, next) => {
  await SongModel.findById(req.params.id, req.body)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully retrieved.",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

// GET ALL
SongRoute.route("/", cors(corsOptionsDelegate)).get(async (req, res, next) => {
  await SongModel.find()
    .then((result) => {
      res.writeHead(201, { "Content-Type": "application/json" });
      res.end(JSON.stringify(result));
    })
    .catch((err) => {
      return next(err);
    });
});

// UPDATE
SongRoute.route("/update-song/:id").put(async (req, res, next) => {
  await SongModel.findByIdAndUpdate(req.params.id, {
    $set: req.body,
  })
    .then((result) => {
      res.json({
        data: result,
        msg: "Data successfully updated.",
      });
    })
    .catch((err) => {
      console.log(err);
    });
});

// // DELETE
SongRoute.route("/delete-song/:id").delete(async (req, res) => {
  await SongModel.findByIdAndRemove(req.params.id)
    .then(() => {
      res.json({
        msg: "Data successfully updated.",
      });
    })
    .catch((err) => {
      console.log(err);
    });
});

module.exports = SongRoute;

In the next step we will configure node/express server, create index.js file in the backend folder’s root and add the given below code inside of it.

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const bodyParser = require("body-parser");
const createError = require("http-errors");

// Connecting MongoDB
async function mongoDbConnection() {
  await mongoose.connect(
    "mongodb://127.0.0.1:27017/test",
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    },
    6000
  );
}
mongoDbConnection().then(() => {
  console.log("MongoDB successfully connected.");
}),
  (err) => {
    console.log("Could not connected to database : " + err);
  };

const songRoute = require("./routes/song.route");
const app = express();

app.use(
  bodyParser.urlencoded({
    extended: true,
  }),
  bodyParser.json()
);


// CORS
app.use(cors());

// RESTful API root
app.use("/api", songRoute);

// PORT
const port = process.env.PORT || 4000;

app.listen(port, () => {
  console.log("PORT Connected on: " + port);
});

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

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

Run the below command while staying in the backend folder.

npx nodemon server

Make sure to setup and start the MongoDB community and MongoDB Compass GUI database in your local system. Also, pass the MongoDB database URL to the mongoose.connect(‘…’) method in the backend/index.js file.

REST API URL – http://localhost:4000/api

Method API url
GET All /api
GET Single /api/get-song/id
POST /api/create-song
PUT /api/update-song/id
DELETE /api/delete-song/id

Create Ionic Angular API Service

We will take the help of Ionic Service to manage REST API in our CRUD mobile app. To make API calls, we need to import HttpClientModule service in the Ionic Angular project.

Head over to src/app/app.module.ts then import and register `HttpClientModule`.

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

@NgModule({
  declarations: [...],
  entryComponents: [...],
  imports: [
    HttpClientModule
  ],
  providers: [...],
  bootstrap: [...]
})

export class AppModule { }

Run the following command to create Ionic Service for handling REST API calls inside the shared folder:

ng generate service shared/song

For type checking, we need to create a Song class. Next, create shared/song.ts file in it. Include the given below code in it to define the song data type.

export class Song {
  _id: string;
  song_name: string;
  artist: string;
}

Next, place the following code in shared/song.service.ts:

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

@Injectable({
  providedIn: 'root',
})
export class SongService {
  httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
  };

  constructor(private httpClient: HttpClient) {}

  addSong(song: Song): Observable<any> {
    return this.httpClient
      .post<Song>(
        'http://localhost:4000/api/create-song',
        song,
        this.httpOptions
      )
      .pipe(catchError(this.handleError<Song>('Add Song')));
  }

  getSong(id: any): Observable<Song[]> {
    return this.httpClient
      .get<Song[]>('http://localhost:4000/api/get-song/' + id)
      .pipe(
        tap((_) => console.log(`Song fetched: ${id}`)),
        catchError(this.handleError<Song[]>(`Get Song id=${id}`))
      );
  }

  getSongList(): Observable<Song[]> {
    return this.httpClient.get<Song[]>('http://localhost:4000/api').pipe(
      tap((_) => console.log('Songs fetched.')),
      catchError(this.handleError<Song[]>('Get Songs', []))
    );
  }

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

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

  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);
    };
  }
}

To make the Http request we imported HttpClientModule in AppModule, now import RxJS operators to send the HTTP request across the server and get the response. Import observable, of, catchError and tap along with HttpClient and HttpHeaders.

Display Data List & Delete

Now, we will display data list in Ionic mobile app. Open home/home.page.ts file and add the following code in it.

import { Component, OnInit } from '@angular/core';
import { SongService } from './../shared/song.service';

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

export class HomePage implements OnInit {
  Songs: any = [];

  constructor(private songService: SongService) {}

  ngOnInit() {}

  ionViewDidEnter() {
    this.songService.getSongList().subscribe((res) => {
      this.Songs = res;
    });
  }

  deleteSong(song: any, i: any) {
    if (window.confirm('Do you want to delete user?')) {
      this.songService.deleteSong(song._id).subscribe(() => {
        this.Songs.splice(i, 1);
        console.log('Song deleted!');
      });
    }
  }
}

Import the Angular service and inject into the constructor it will allow us to consume the REST APIs and render the data into the Ionic view.

We use the ionViewDidEnter() page life cycle hook this hook will update the data in the Ionic view if the data is updated in the database.

We also declared the deleteSong() function it helps in removing the song data from the Ionic as well as the mongoDB database.

Next, open the home/home.page.html file and include the following code in it.

<ion-header>
  <ion-toolbar>
    <ion-title> Ionic Music App </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list class="ios list-ios hydrated">
    <ion-list-header class="ios hydrated"> Song List </ion-list-header>

    <ion-item
      *ngFor="let song of Songs"
      class="item-label item ios in-list ion-focusable hydrated"
    >
      <ion-label class="sc-ion-label-ios-h sc-ion-label-ios-s ios hydrated">
        <h2>{{song.song_name}}</h2>
        <h3>{{song.artist}}</h3>
      </ion-label>

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

  <!-- fab placed to the bottom start -->
  <ion-fab
    vertical="bottom"
    horizontal="end"
    slot="fixed"
    routerLink="/add-song"
  >
    <ion-fab-button>
      <ion-icon name="add"></ion-icon>
    </ion-fab-button>
  </ion-fab>
</ion-content>

Here, we used the Ionic HTML to display the data list in the Ionic view we are showing the data with the help of Angular’s *ngFor directive. Declare the routerLink directive and pass the edit-song route to navigate to the edit page.

To know more about Ionic’s UI components check out here.

Add Data in Ionic

To add the data, we need to import and register FormsModule and ReactiveFormsModule in page’s module. Now, we need to remember that we have to import these Angular 9 form services in every page’s module rather than using it globally in the app.module.ts file.

Open add-song.module.ts file and add the following code.

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

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
})

Open add-song.page.ts file and add the following code.

import { Component, OnInit, NgZone } from '@angular/core';
import { SongService } from './../shared/song.service';
import { Router } from '@angular/router';
import { FormGroup, FormBuilder } from '@angular/forms';

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

export class AddSongPage implements OnInit {
  songForm: FormGroup;

  constructor(
    private songAPI: SongService,
    private router: Router,
    public fb: FormBuilder,
    private zone: NgZone
  ) {
    this.songForm = this.fb.group({
      song_name: [''],
      artist: [''],
    });
  }

  ngOnInit() {}

  onFormSubmit() {
    if (!this.songForm.valid) {
      return false;
    } else {
      return this.songAPI.addSong(this.songForm.value).subscribe(() => {
        this.zone.run(() => {
          this.router.navigate(['/home']);
          this.songForm.reset();
        });
      });
    }
  }
}

Open add-song.page.html file and add the following code.

<ion-header>
  <ion-toolbar class="ios hydrated">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="home"></ion-back-button>
    </ion-buttons>
    <ion-title class="ios title-ios hydrated">Add Song</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list
    lines="full"
    class="ion-no-margin ion-no-padding ios list-ios list-lines-full list-ios-lines-full hydrated">
    <form [formGroup]="songForm" (ngSubmit)="onFormSubmit()">
      <ion-item>
        <ion-label position="floating">Song name</ion-label>
        <ion-input
          formControlName="song_name"
          aria-label="Song name"
          type="text"
          required></ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">Artist</ion-label>
        <ion-input
          formControlName="artist"
          aria-label="Artist"
          type="text"
          required>
        </ion-input>
      </ion-item>

      <ion-row>
        <ion-col>
          <ion-button type="submit" color="primary" shape="full" expand="block">Add Song</ion-button>
        </ion-col>
      </ion-row>
    </form>
  </ion-list>
</ion-content>

Ionic Edit Data

To edit the data you need to open the edit-song.module.ts file and add the following code in it.

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

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
})

Next, open the edit-song.page.ts file and add the following code inside of it.

import { Component, OnInit } from '@angular/core';
import { SongService } from './../shared/song.service';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroup, FormBuilder } from '@angular/forms';

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

export class EditSongPage implements OnInit {
  updateSongForm: FormGroup;
  id: any;

  constructor(
    private songAPI: SongService,
    private actRoute: ActivatedRoute,
    private router: Router,
    public fb: FormBuilder
  ) {
    this.id = this.actRoute.snapshot.paramMap.get('id');
  }

  ngOnInit() {
    this.getSongData(this.id);
    this.updateSongForm = this.fb.group({
      song_name: [''],
      artist: [''],
    });
  }

  getSongData(id: any) {
    this.songAPI.getSong(id).subscribe((data: any) => {
      this.updateSongForm.setValue({
        song_name: data.data['song_name'],
        artist: data.data['artist'],
      });
    });
  }

  updateForm() {
    if (!this.updateSongForm.valid) {
      return false;
    } else {
      return this.songAPI
        .updateSong(this.id, this.updateSongForm.value)
        .subscribe((res) => {
          this.updateSongForm.reset();
          this.router.navigate(['/home']);
        });
    }
  }
}

Next, open the edit-song.page.html file and add the following code inside of it.

<ion-header>
  <ion-toolbar class="ios hydrated">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="home"></ion-back-button>
    </ion-buttons>
    <ion-title class="ios title-ios hydrated">Edit Song</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list
    lines="full"
    class="ion-no-margin ion-no-padding ios list-ios list-lines-full list-ios-lines-full hydrated"
  >
    <form [formGroup]="updateSongForm" (ngSubmit)="updateForm()">
      <ion-item>
        <ion-label position="floating">Song name</ion-label>
        <ion-input
          formControlName="song_name"
          aria-label="Song name"
          type="text"
          required
        ></ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">Artist</ion-label>
        <ion-input
          formControlName="artist"
          aria-label="Artist"
          type="text"
          required
        >
        </ion-input>
      </ion-item>

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

Test App in Browser

Next, To start the app in both iOS and Android mode in the browser, use below command to install lab mode as a development dependency.

npm i @ionic/lab --legacy-peer-deps

Then, run the command in the terminal. to open the Ionic CRUD mobile app in the browser emulator.

ionic serve -l

Conclusion

In this tutorial we learned how to create an Ionic Hybrid CRUD mobile application using the latest technologies.

Also, we have learned how to create REST APIs using the Node/Express and save the data from the Ionic frontend to MongoDB database.

To compare your code with this tutorial, you can download the complete code from this Github.

If it genuinely solved your problem, then bless my repo with a STAR.