Build Angular 16 CRUD Application with Node Js REST API

Last Updated on by in Angular

This is a step-by-step guide on how to build MEAN Stack CRUD application using Angular 16, Node 18+, MongoDB Community Server + MongoDB Compass GUIAngular. To manage the data we will use the Express js.

Angular CRUD refers to the implementation of Create, Read, Update, and Delete operations in an Angular application.

CRUD is a common acronym in software development that represents the basic operations performed on persistent data: Create (C), Read (R), Update (U), and Delete (D).

Angular MEAN stack with MongoDB refers to a development stack that combines Angular, MongoDB, Express.js, and Node.js to build web applications.

REST stands for Representational state transfer; it is a software architectural style that describes limitations for creating Web services. Web services that adhere to the REST architectural style is known as RESTful Web services.

We will be using Node and Express JavaScript frameworks for creating REST API for Create, Read, Update and Delete operations from the absolute beginning.

If you are a novice developer and just getting started to learn MEAN stack development, then rest assured this Angular tutorial will give you innumerable benefits and help you propel forward your development career.

Let’s start developing CRUD Operations in Angular 16 with RESTful API:

Create Angular Project

Open console, run command to generate the new Angular project:

ng new angular-mean-crud-tutorial

Move inside the project root:

cd angular-mean-crud-tutorial

Execute command to install Bootstrap package:

npm install bootstrap

Place Bootstrap CSS path in angular.json file:

"styles": [
     "src/styles.scss",
     "node_modules/bootstrap/dist/css/bootstrap.min.css"
]

No Property Access From Index Signature

To resolve error:

Property ‘xxxName’ comes from an index signature, so it must be accessed with [‘xxxName’]

This setting makes sure profound consistency between accessing a field via the “dot” (obj.key) syntax, and “indexed” (obj["key"]) and the way which the property is declared in the type.

Without this flag, TypeScript will allow you to use the dot syntax to access fields which are not defined:

Make sure to set noPropertyAccessFromIndexSignature property to false under compilerOptions in tsconfig.json file:

"compilerOptions": {
// ...
// ...
   "noPropertyAccessFromIndexSignature": false,
// ...
// ...
}

Create Components in Angular

Execute command to generate a couple of components that will be used for the Angular CRUD project.

ng g c components/add-book
ng g c components/book-detail
ng g c components/books-list

You have just generated the add-book, book-detail, and books-list components folder.

Create CRUD Routes

Next, create routes; with the help of Angular routes, we will make the consensus with components to enable the navigation in the CRUD application so add the below code in the app-routing.module.ts file.

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

import { BooksListComponent } from './components/books-list/books-list.component';
import { AddBookComponent } from './components/add-book/add-book.component';
import { BookDetailComponent } from './components/book-detail/book-detail.component';

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'add-book' },
  { path: 'books-list', component: BooksListComponent },
  { path: 'add-book', component: AddBookComponent },
  { path: 'edit-book/:id', component: BookDetailComponent }
];

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

export class AppRoutingModule { }

Import HttpClientModule, FormsModule & ReactiveFormsModule

In general, in the CRUD application, HTTP requests are made to send and update the server’s data. It requires working on Form data and HTTP calls; consequently, we need to import and register HttpClientModule, FormsModule and ReactiveFormsModule in the app.module.ts file.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

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

import { AppRoutingModule } from './app-routing.module';
import { AddBookComponent } from './components/add-book/add-book.component';
import { BookDetailComponent } from './components/book-detail/book-detail.component';
import { BooksListComponent } from './components/books-list/books-list.component';

@NgModule({
  declarations: [
    AppComponent,
    AddBookComponent,
    BookDetailComponent,
    BooksListComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})

export class AppModule {}

Build RESTful API with Node and Express

Now, comes the essential part of this tutorial. In this tutorial, we will learn how to create RESTful API with Node and Express.js, not just that to handle the data we will learn to use mongoDB.

You need to build the backend separately, so execute the below command to invoke the REST API development with Node and Express.js.

mkdir node-rest-api && cd node-rest-api

Evoke npm initializer to set up a new npm package in the node-rest-api folder.

npm init -y

Define name, version, description, main and author name to new node project.

Run command to install imperative npm packages which will help us to create REST APIs for our Angular CRUD system.

npm install express cors body-parser mongoose

To automate the server restarting process install nodemon package as a dev dependency.

npm install nodemon --save-dev

Now, you need to create the Book model or schema, create node-rest-api/model folder. Also create a Book.js file within and place the below code.

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

let Book = new Schema({
  name: {
    type: String
  },
  price: {
    type: String
  },
  description: {
    type: String
  }
}, {
  collection: 'books'
})

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

You need to define the REST API routes using Express js in a node project. Create node-rest-api/routes folder, also create book.routes.js file, place the below code within.

const express = require("express");
const app = express();
const bookRoute = express.Router();
let Book = require("../model/Book");

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

// GET ALL
bookRoute.route("/").get(async (req, res, next) => {
  await Book.find()
    .then((result) => {
      res.json({
        data: result,
        message: "All items successfully fetched.",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

// GET SIGNLE
bookRoute.route("/read-book/:id").get(async (req, res, next) => {
  await Book.findById(req.params.id)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully fetched.",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

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

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

module.exports = bookRoute;

Now, you need to sum up all the code and conjugate at one place so that we can run our backend and propel the CRUD app development forward.

Create and add the below code in node-rest-api/index.js file.

const express = require("express");
const path = require("path");
const mongoose = require("mongoose");
const cors = require("cors");
const bodyParser = require("body-parser");

mongoose
  .connect("mongodb://127.0.0.1:27017/test")
  .then((x) => {
    console.log(
      `Connected to Mongo! Database name: "${x.connections[0].name}"`
    );
  })
  .catch((err) => {
    console.error("Error connecting to mongo", err.reason);
  });

const bookRoute = require("./routes/book.routes");
const app = express();

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

app.use(cors());

// Static directory path
app.use(
  express.static(path.join(__dirname, "dist/angular-mean-crud-tutorial"))
);

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

// PORT
const port = process.env.PORT || 8000;
app.listen(port, () => {
  console.log("Listening on port " + port);
});

// 404 Handler
app.use((req, res, next) => {
  next(createError(404));
});

// Base Route
app.get("/", (req, res) => {
  res.send("invaild endpoint");
});
app.get("*", (req, res) => {
  res.sendFile(
    path.join(__dirname, "dist/angular-mean-crud-tutorial/index.html")
  );
});

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

Node and Express REST API Example

Now, we understand how to run Node/Express backend server:

To start the node and express server, you must have MongoDB installed on your local development system; check out how to install MongoDB community edition on your local machine.

Once the MongoDB community edition has been set up, make sure to start the MongoDB on your local machine then follow the subsequent step.

Next, execute the command while staying in the server folder (node-rest-api):

npx nodemon server

Here is your bash URL for REST API built with Node and Express http://localhost:8000/api

The endpoints we created and you can use these to handle the CRUD operations with Angular application:

Methods Endpoints
GET /api
POST /add-book
GET /read-book/id
PUT /update-book/id
DELETE /delete-book/id

Create Angular Service for REST API Consumption

Theoretically, we need to keep the Angular Service and Model in a separate folder, so create app/service folder in Angular project and create Book.ts class within:

Then, add the below code in app/service/Book.ts file.

export class Book {
    _id!: String;
    name!: String;
    price!: String;
    description!: String;
}

Likewise, execute the command to create crud service file:

ng g s service/crud

Then, add the below code in app/service/crud.service.ts file:

import { Injectable } from '@angular/core';
import { Book } from './Book';
import { catchError, map } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import {
  HttpClient,
  HttpHeaders,
  HttpErrorResponse,
} from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})

export class CrudService {
  // Node/Express API
  REST_API: string = 'http://localhost:8000/api';

  // Http Header
  httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');

  constructor(private httpClient: HttpClient) {}

  // Add
  AddBook(data: Book): Observable<any> {
    let API_URL = `${this.REST_API}/add-book`;
    return this.httpClient
      .post(API_URL, data)
      .pipe(catchError(this.handleError));
  }

  // Get all objects
  GetBooks() {
    return this.httpClient.get(`${this.REST_API}`);
  }

  // Get single object
  GetBook(id: any): Observable<any> {
    let API_URL = `${this.REST_API}/read-book/${id}`;
    return this.httpClient.get(API_URL, { headers: this.httpHeaders }).pipe(
      map((res: any) => {
        return res || {};
      }),
      catchError(this.handleError)
    );
  }

  // Update
  updateBook(id: any, data: any): Observable<any> {
    let API_URL = `${this.REST_API}/update-book/${id}`;
    return this.httpClient
      .put(API_URL, data, { headers: this.httpHeaders })
      .pipe(catchError(this.handleError));
  }

  // Delete
  deleteBook(id: any): Observable<any> {
    let API_URL = `${this.REST_API}/delete-book/${id}`;
    return this.httpClient
      .delete(API_URL, { headers: this.httpHeaders })
      .pipe(catchError(this.handleError));
  }

  // Error
  handleError(error: HttpErrorResponse) {
    let errorMessage = '';
    if (error.error instanceof ErrorEvent) {
      // Handle client error
      errorMessage = error.error.message;
    } else {
      // Handle server error
      errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    console.log(errorMessage);
    return throwError(() => {
      errorMessage;
    });
  }
}

Adding Navigation with Bootstrap

To configure navigation define the router-outlet directive, routerLink directive with angular routes in app.component.html file:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand">Angular Mean Stack 16 CRUD App Example</a>

  <div id="navbarNav" class="collapse navbar-collapse">
    <ul class="navbar-nav ml-auto">
      <li class="nav-item">
        <a class="nav-link" routerLinkActive="active" routerLink="/books-list"
          >Show Books</a
        >
      </li>
      <li class="nav-item">
        <a class="nav-link" routerLinkActive="active" routerLink="/add-book"
          >Add Books</a
        >
      </li>
    </ul>
  </div>
</nav>

<router-outlet></router-outlet>

Build Create Operation

This step comprises of creating or adding data to the MongoDB database using Angular and Node/Express REST API.

Add the code in add-book.component.ts file:

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

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

export class AddBookComponent implements OnInit {
  bookForm: FormGroup;

  constructor(
    public formBuilder: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private crudService: CrudService
  ) {
    this.bookForm = this.formBuilder.group({
      name: [''],
      price: [''],
      description: [''],
    });
  }

  ngOnInit() {}

  onSubmit(): any {
    this.crudService.AddBook(this.bookForm.value).subscribe(
      (res: any) => {
        console.log('Data added successfully!' + res);
        this.ngZone.run(() => this.router.navigateByUrl('/books-list'));
      },
      (err: any) => {
        console.log(err);
      }
    );
  }
}

Add the code in add-book.component.html file:

<div class="row justify-content-center mt-5">
  <div class="col-md-4">
    <form [formGroup]="bookForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label>Name</label>
        <input
          class="form-control"
          type="text"
          formControlName="name"
          required
        />
      </div>

      <div class="form-group">
        <label>Price</label>
        <input
          class="form-control"
          type="text"
          formControlName="price"
          required
        />
      </div>

      <div class="form-group">
        <label>Description</label>
        <input
          class="form-control"
          type="text"
          formControlName="description"
          required
        />
      </div>

      <div class="form-group">
        <button class="btn btn-primary btn-block" type="submit">
          Add Book
        </button>
      </div>
    </form>
  </div>
</div>

Render Data Object and Show as a List

The Book objects are being fetched from the database using the CrudService; simultaneously, a single book object is being deleted using the delete() method.

Add the code in books-list.component.ts file:

import { Component, OnInit } from '@angular/core';
import { CrudService } from './../../service/crud.service';

@Component({
  selector: 'app-books-list',
  templateUrl: './books-list.component.html',
  styleUrls: ['./books-list.component.scss'],
})
export class BooksListComponent implements OnInit {
  Books: any = [];

  constructor(private crudService: CrudService) {
    this.crudService.GetBooks().subscribe((data: any) => {
      this.Books = data.data;
    });
  }

  ngOnInit() {}

  delete(id: any, i: any) {
    if (window.confirm('Do you want to go ahead?')) {
      this.crudService.deleteBook(id).subscribe((data: any) => {
        this.Books.splice(i, 1);
      });
    }
  }
}

Add the code in books-list.component.html file:

<div class="container">
  <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h2 class="h2">Books List</h2>
  </div>

  <div class="table-responsive">
    <table class="table table-bordered">
      <thead>
        <tr>
          <th scope="col">Id</th>
          <th scope="col">Name</th>
          <th scope="col">Price</th>
          <th scope="col">Description</th>
          <th class="text-center" scope="col">Action</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let book of Books; let i = index">
          <th scope="row">{{book._id}}</th>
          <td>{{book.name}}</td>
          <td>{{book.price}}</td>
          <td>{{book.description}}</td>
          <td class="text-center">
            <button class="btn btn-sm btn-primary" routerLink="/edit-book/{{book._id}}">Edit</button>
            <button class="btn btn-sm btn-danger" (click)="delete(book._id, i)">Delete</button>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Create and Edit Details Page

Generically, we are using ActivatedRoute router API to get the object ID from the URL; based on the key, we are accessing the GetBook() method to fetch the book object. Also, using the updateBook method to update the data on the database.

Add the code in book-detail.component.ts file:

import { Component, OnInit, NgZone } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { CrudService } from './../../service/crud.service';
import { FormGroup, FormBuilder } from "@angular/forms";

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

export class BookDetailComponent implements OnInit {

  getId: any;
  updateForm: FormGroup;
  
  constructor(
    public formBuilder: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private activatedRoute: ActivatedRoute,
    private crudService: CrudService
  ) {
    this.getId = this.activatedRoute.snapshot.paramMap.get('id');

    this.crudService.GetBook(this.getId).subscribe((data: any) => {
      this.updateForm.setValue({
        name: data.data.name,
        price: data.data.price,
        description: data.data.description
      });
    });

    this.updateForm = this.formBuilder.group({
      name: [''],
      price: [''],
      description: ['']
    })
  }

  ngOnInit() { }

  onUpdate(): any {
    this.crudService.updateBook(this.getId, this.updateForm.value)
    .subscribe(() => {
        console.log('Data updated successfully!')
        this.ngZone.run(() => this.router.navigateByUrl('/books-list'))
      }, (err) => {
        console.log(err);
    });
  }

}

Add the code in book-detail.component.html file:

<div class="row justify-content-center mt-5">
  <div class="col-md-4">
    <form [formGroup]="updateForm" (ngSubmit)="onUpdate()">
      <div class="form-group">
        <label>Name</label>
        <input class="form-control" type="text" formControlName="name" required>
      </div>

      <div class="form-group">
        <label>Price</label>
        <input class="form-control" type="text" formControlName="price" required>
      </div>

      <div class="form-group">
        <label>Description</label>
        <input class="form-control" type="text" formControlName="description" required>
      </div>

      <div class="form-group">
        <button class="btn btn-primary btn-block" type="submit">Update</button>
      </div>
    </form>
  </div>
</div>

Finally, start the app development server of angular application:

ng serve --open

Conclusion

Angular eases the process of integrating CRUD operations by offering intuitive features like data binding, forms, HTTP services, and observables.

These features empowers front-developers to efficiently manage data in an Angular application and interact with backend systems to perform CRUD operations.

In this Angular CRUD example, we learned to build REST API and consume REST API with Angular service, not just that we created the locus of data and learned how to store the data in the MongoDB database.

I hope you will like this learning paradigm and share this tutorial with others.

You can download the full code from GitHub.