Angular 10 + Node.js + MongoDB - CRUD example - MEAN stack development example

Hello everyone, today we will learn how to develop a MEAN stack web application that is a basic User Management Application using MongoDB, Express, Angular 10, and Node.js.GitHub repository link is provided at the end of this tutorial. You can download the source code.

Architecture

We’re gonna build the application with the following architecture:


  • Node.js Express exports REST APIs & interacts with MongoDB Database utilizing Mongoose ODM.
  • Angular Client sends HTTP Requests and retrieves HTTP Replications utilizing HTTPClient, consume data on the components. Angular Router is utilized for navigating to pages.
User Interface

-Add a User:

-Retrieve all Users:

-Update User:

-User by ID:



We divided this tutorial into two parts.

PART 1 - Rest APIs Development using Node.js and express
PART 2 - UI development using Angular 10

PART 1 - Rest APIs Development using Node.js and express

These are APIs that Node.js Express App will export:

  1. GET all User's        :     /api/v1/users
  2. GET User by ID     :     /api/v1/users/{_id}
  3. POST User             :     /api/v1/users
  4. PUT User               :     /api/v1/users/{_id}
  5. DELETE User       :     /api/v1/users/{_id}
Back end project structure


The package.json file

A package. json is a JSON file that subsists at the root of a Javascript/Node project. It holds metadata pertinent to the project and it is utilized for managing the project's dependencies, scripts, version, and a whole lot more.

{
"name": "node_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon index.ts",
"seed": "ts-node ./seed/seed.ts",
"lint": "eslint . --ext .ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"@types/express-fileupload": "^1.1.5",
"@types/jsonwebtoken": "^8.5.0",
"@types/mongoose": "^5.7.20",
"@types/node": "^14.0.1",
"@types/properties-reader": "^2.1.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"confi": "^9.10.1",
"cors": "^2.8.5",
"custom-env": "^2.0.1",
"del": "^6.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.20",
"lokijs": "^1.5.11",
"mocha": "^8.2.1",
"mongodb": "^3.5.7",
"mongoose": "^5.9.14",
"mongoose-seed": "^0.6.0",
"morgan": "^1.10.0",
"multer": "^1.4.2",
"properties-reader": "^2.1.1",
"ts-node": "^8.10.1",
"typescript": "^3.9.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"nodemon": "^2.0.3"
}
}

The db.ts file

import mongoose = require('mongoose');


import UserDoc = require('./models/user.model')
export const connectionString = 'mongodb://localhost/user-dev';

mongoose.connect(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
});

export const models = { UserDoc };

The collection.ts file

const collections = {
users: 'users'
};

export = collections

The user.model.ts file inside the model package

import mongoose from 'mongoose';
import collections from '../collections';
import { User } from '../interface/user.interface';

const schema = new mongoose.Schema(
{
firstName: {
type: String,
},
lastName: {
type: String
},
emailId: {
type: String,
unique: true
}
},
);

schema.set('toJSON', { virtuals: true });

export = mongoose.model<User>(collections.users, schema);

The user.interface.ts file inside the interface package

import { Document } from 'mongoose';

export interface User extends Document{
firstName: string;
lastName: string;
emailId: string;
}

The user.routes.ts file

import { Router, Request, Response } from 'express';
import { updateUser, findById, deleteUserById,saveUserData,getAllUsers }
from '../services/user.service';

const router = Router();

router.post('/users', (req: Request, res: Response) => {
saveUserData(req, res);
});
router.put('/users/:id', (req: Request, res: Response) => {
updateUser(req, res);
});
router.get('/users', (req: Request, res: Response) => {
getAllUsers(req, res);
});
router.delete('/users/:id', (req: Request, res: Response) => {
deleteUserById(req, res);
});
router.get('/users/:id', (req: Request, res: Response) => {
findById(req, res);
});
export = router

The user.service.ts file

import { Request, Response } from 'express';
import { models } from '../db/db';
const { UserDoc } = models;

//get all user
export async function getAllUsers(request: Request, response: Response):
Promise<Response> {

try {
const data = await UserDoc.find();
console.log(data)
return response.status(200).send(data);
} catch (error) {
console.log(Error)
return response.status(500).send({ message: "Technical Error" });
}
}

//Save new user
export async function saveUserData(request: Request, response: Response):
Promise<Response> {
console.log(request.body)
try {
const data = new UserDoc(request.body);
await data.save();
return response.status(200).send({ data });
} catch (error) {
console.log(error)
return response.status(500).send({ message: "Technical Error" });
}
}

//get user by id
export async function findById(request: Request, response: Response):
Promise<Response> {

try {
const data = await UserDoc.findById(request.params.id);
return response.status(200).send(data);
} catch (error) {
console.log(Error)
return response.status(500).send({ message: "Technical Error" });
}

}

//delete user
export async function deleteUserById(request: Request, response: Response):
Promise<Response> {

try {
console.log(request.params.id)
const data = await UserDoc.remove({ _id: request.params.id });
console.log(data)
return response.status(200).send({ data: "Deleted Successfully" });
} catch (error) {
console.log(Error)
return response.status(500).send({ message: "Technical Error" });
}
}

export async function updateUser(request: Request, response: Response):
Promise<Response> {

try {
console.log(request.body)
const data = await UserDoc.findOneAndUpdate({ _id: request.params.id },
request.body, { new: true });
console.log(data)
return response.status(200).send({ data: "Successfully Updated" });
} catch (error) {
console.log(Error)
return response.status(500).send({ message: "Technical Error" });
}

}

The index.ts file

index.ts is similar to index.js in node.js or index.html is web site hosting. So when you say import {} from 'directory_name' it will look for index.ts inside the specified directory and import whatever is exported there.

import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import user from './routes/user.routes';
const morgan = require('morgan');
const fileUpload = require('express-fileupload');
const fs = require('fs');
//if (process.env.NODE_ENV !== 'production') {
//require('dotenv').config();
//}

const app = express();
app.use(fileUpload({
createParentPath: true
}));

app.use(morgan('common', {
stream: fs.createWriteStream('./' + new Date().toISOString().substr(0, 10)
+ '.log', { flags: 'a' })
}));
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());
app.use('/api/v1/', user)
app.listen(8000, () => {
// eslint-disable-next-line no-console
console.log('App listening on port 8000!');
});

Download the dependencies using the following command

npm install

Run our backend application using the following command

npm start

Console output:

> node_app@1.0.0 start /home/sibinmuhammed/Downloads/cfbhfgdfgdfg/Mean-Stack-CRUD-main/back-end-nodejs
> nodemon index.ts

[nodemon] 2.0.6
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node index.ts`
App listening on port 8000!

Using postman or any other API tester you can test the endpoints.


PART 2 - UI development using Angular 10

Now we are going to develop an Angular 10 web application.

package structure - Front end



The package.json file

A package.json is a JSON file that subsists at the root of a Javascript/Node project. It holds metadata pertinent to the project and it is utilized for managing the project's dependencies, scripts, version, and a whole lot more.

{
"name": "front-end-simple-crud",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~10.1.2",
"@angular/common": "~10.1.2",
"@angular/compiler": "~10.1.2",
"@angular/core": "~10.1.2",
"@angular/forms": "~10.1.2",
"@angular/platform-browser": "~10.1.2",
"@angular/platform-browser-dynamic": "~10.1.2",
"@angular/router": "~10.1.2",
"bootstrap": "^4.5.3",
"jquery": "^3.5.1",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.1100.2",
"@angular/cli": "~10.1.2",
"@angular/compiler-cli": "~10.1.2",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2"
}
}

Components
  • create-user
  • update-user
  • user-details
  • user-list
Services

user.service.ts

Modules
  • FormsModule
  • HttpClientModule
  • AppRoutingModule
User class

user.ts

Let's auto-generate the service and components using Angular CLI.

ng g s user
– ng g c create-user
– ng g c user-details
– ng g c user-list
– ng g c update-user

The User.ts file(User Model)

Path - src/app/user.ts 
Before defining the UserListComponent, let’s define a User class for working with users. create a new file user.ts inside src/app folder and add the following code to it -

export class User {
_id: string;
firstName: string;
lastName: string;
emailId: string;
active: boolean;

}

Create User List Template and Component

User List Component

Path - src/app/user-list/user-list.component.ts

Let's create the UserListComponent component which will be used to display a list of users, create a new user, and delete a user.

import { UserDetailsComponent } from '../user-details/user-details.component';
import { Observable } from "rxjs";
import { UserService } from "../user.service";
import { User } from "../user";
import { Component, OnInit } from "@angular/core";
import { Router } from '@angular/router';

@Component({
selector: "app-user-list",
templateUrl: "./user-list.component.html",
styleUrls: ["./user-list.component.css"]
})
export class UserListComponent implements OnInit {
users: Observable<User[]>;

constructor(private userService: UserService,
private router: Router) { }

ngOnInit() {
this.reloadData();
}

reloadData() {
this.users = this.userService.getUsersList();
}

deleteUser(_id: string) {
this.userService.deleteUser(_id)
.subscribe(
data => {
console.log(data);
this.reloadData();
},
error => console.log(error));
}
updateUser(id: string) {
this.router.navigate(['update', id]);
}
userDetails(_id: string) {
this.router.navigate(['details', _id]);
}
}

User List Template

Path - src/app/user-list/user-list.component.html
Add user-list.component.html file with the following code to it -

<div class="panel panel-primary">
<div class="panel-heading">
<h2>User List</h2>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users | async">
<td>{{user.firstName}}</td>
<td>{{user.lastName}}</td>
<td>{{user.emailId}}</td>
<td>
<button (click)="updateUser(user.id)" class="btn btn-warning"
style="margin-left: 10px">Update</button>
<button (click)="deleteUser(user._id)" class="btn btn-danger"
style="margin-left: 10px">Delete</button>
<button (click)="userDetails(user._id)" class="btn btn-info"
style="margin-left: 10px">Details</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

Create Add User Template and Component

Add User Component

Path - src/app/create-user/create-user.component.ts

CreateUserComponent is used to create and handle a new user form data. Add the following code to it -

import { UserService } from '../user.service';
import { User } from '../user';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

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

user: User = new User();
submitted = false;

constructor(private userService: UserService,
private router: Router) { }

ngOnInit() {
}

newUser(): void {
this.submitted = false;
this.user = new User();
}

save() {
this.userService
.createUser(this.user).subscribe(data => {
console.log(data)
this.user = new User();
this.gotoList();
},
error => console.log(error));
}

onSubmit() {
this.submitted = true;
this.save();
}

gotoList() {
this.router.navigate(['/users']);
}
}

Create User Template

Path - src/app/create-user/create-user.component.html 

The create-user.component.html shows the add using HTML form. Add the following code to it -

<h3>Create User</h3>
<div [hidden]="submitted" style="width: 400px;">
<form (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">First Name</label>
<input type="text" class="form-control" id="firstName"
required [(ngModel)]="user.firstName" name="firstName">
</div>

<div class="form-group">
<label for="name">Last Name</label>
<input type="text" class="form-control" id="lastName"
required [(ngModel)]="user.lastName" name="lastName">
</div>

<div class="form-group">
<label for="name">Email Id</label>
<input type="text" class="form-control" id="emailId"
required [(ngModel)]="user.emailId" name="emailId">
</div>

<button type="submit" class="btn btn-danger">Submit</button>
</form>
</div>

<div [hidden]="!submitted">
<h4>Successfully submitted!</h4>
</div>

Update User Template and Component

Update User Component

Path - src/app/update-user/update-user.component.ts 

UpdateUserComponent is used to update an existing user.

import { Component, OnInit } from '@angular/core';
import { User } from '../user';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from '../user.service';

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

_id: string;
user: User;

constructor(private route: ActivatedRoute, private router: Router,
private userService: UserService) { }

ngOnInit() {
this.user = new User();

this._id = this.route.snapshot.params['_id'];

this.userService.getUser(this._id)
.subscribe(data => {
this.user = data;
}, error => console.log(error));
}

updateUser() {
this.userService.updateUser(this._id, this.user)
.subscribe(data => {
console.log(data);
this.user = new User();
this.gotoList();
}, error => console.log(error));
}

onSubmit() {
this.updateUser();
}

gotoList() {
this.router.navigate(['/users']);
}
}

Update User Template

Path - src/app/update-user/update-user.component.html 

The update-user.component.html shows the update user HTML form. Add the following code to this 
file -

<h3>Update User</h3>
<div style="width: 500px;">
<form (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">First Name</label>
<input type="text" class="form-control" id="firstName"
required [(ngModel)]="user.firstName" name="firstName">
</div>

<div class="form-group">
<label for="name">Last Name</label>
<input type="text" class="form-control" id="lastName"
required [(ngModel)]="user.lastName" name="lastName">
</div>

<div class="form-group">
<label for="name">Email Id</label>
<input type="text" class="form-control" id="emailId"
required [(ngModel)]="user.emailId" name="emailId">
</div>

<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>

Create a View User Details Template and Component

User Details Component

Path - src/app/user-details/user-details.component.ts 

The UserDetailsComponent component used to display a particular user detail. Add the following code to it -

import { User } from '../user';
import { Component, OnInit, Input } from '@angular/core';
import { UserService } from '../user.service';
import { Router, ActivatedRoute } from '@angular/router';

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

_id: string;
user: User;

constructor(private route: ActivatedRoute,private router: Router,
private userService: UserService) { }

ngOnInit() {
this.user = new User();

this._id = this.route.snapshot.params['_id'];
console.log("gh"+this._id);
this.userService.getUser(this._id)
.subscribe(data => {
console.log(data)
this.user = data;
}, error => console.log(error));
}

list(){
this.router.navigate(['users']);
}
}

User Details Component Template

Path - src/app/user-details/user-details.component.html 

The user-details.component.html displays a particular user detail. Add the following code to it -

<h2>User Details</h2>

<hr />
<div *ngIf="user">
<div>
<label><b>First Name: </b></label> {{user.firstName}}
</div>
<div>
<label><b>Last Name: </b></label> {{user.lastName}}
</div>
<div>
<label><b>Email Id: </b></label> {{user.emailId}}
</div>
</div>

<br>
<br>
<button (click)="list()" class="btn btn-primary">User List</button><br>

User Service

Path - src/app/user.service.ts 

The UserService will be used to get the data from the backend by calling  APIs. Update the user.service.ts file inside src/app directory with the following code to it -

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class UserService {

private baseUrl = 'http://localhost:8000/api/v1/users';

constructor(private http: HttpClient) { }

getUser(_id: string): Observable<any> {
return this.http.get(`${this.baseUrl}/${_id}`);
}

createUser(user: Object): Observable<Object> {
return this.http.post(`${this.baseUrl}`, user);
}

updateUser(_id: string, value: any): Observable<Object> {
return this.http.put(`${this.baseUrl}/${_id}`, value);
}

deleteUser(_id: string): Observable<any> {
return this.http.delete(`${this.baseUrl}/${_id}`,
{ responseType: 'text' });
}

getUsersList(): Observable<any> {
return this.http.get(`${this.baseUrl}`);
}
}

App Routing Module

Path: /src/app/app.routing.module.ts 

Routing for the Angular app is configured as an array of Routes, each component is mapped to a path so the Angular Router knows which component to display based on the URL in the browser address bar.

import { UserDetailsComponent } from './user-details/user-details.component';
import { CreateUserComponent } from './create-user/create-user.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {UserListComponent } from './user-list/user-list.component';
import { UpdateUserComponent } from './update-user/update-user.component';

const routes: Routes = [
{ path: '', redirectTo: 'user', pathMatch: 'full' },
{ path: 'users', component: UserListComponent },
{ path: 'add', component: CreateUserComponent },
{ path: 'update/:_id', component: UpdateUserComponent },
{ path: 'details/:_id', component: UserDetailsComponent },
];

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

App Component

Path: /src/app/app.component.ts 

The app component is the root component of the application, it defines the root tag of the app as with the selector property of the @Component decorator.

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

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Angular 10 + Node.js + Mongo DB CRUD ';
}

App Component Template

Path: /src/app/app.component.html 

Defines the HTML template associated with the root AppComponent.

<nav class="navbar navbar-expand-sm bg-success navbar-dark">
<!-- Links -->
<ul class="navbar-nav">
<li class="nav-item">
<a routerLink="users" class="nav-link" routerLinkActive="active">
User List</a>
</li>
<li class="nav-item">
<a routerLink="add" class="nav-link" routerLinkActive="active">
Add User</a>
</li>
</ul>
</nav>
<div class="container">
<br>
<h2 style="text-align: center;">{{title}}</h2>
<hr>
<div class="card">
<div class="card-body">
<router-outlet></router-outlet>
</div>
</div>
</div>

<footer class="footer">
<div class="container">
<span>All Rights Reserved 2020 @Knowledge Factory</span>
</div>
</footer>

App Module

Path: /src/app/app.module.ts 

Defines the root module, named AppModule, that tells Angular how to assemble the application. Initially declares only the AppComponent. 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CreateUserComponent } from './create-user/create-user.component';
import { UserDetailsComponent } from './user-details/user-details.component';
import { UserListComponent } from './user-list/user-list.component';
import { UpdateUserComponent } from './update-user/update-user.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
CreateUserComponent,
UserDetailsComponent,
UserListComponent,
UpdateUserComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

index.html

Path: /src/index.html 

The main index.html file is the initial page loaded by the browser that kicks everything off.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular10CRUD</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

Main/Bootstrap File

Path: /src/main.ts The main file is the entry point used by angular to launch
and bootstrap the application.

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

Download the dependencies using the following command

npm install

Run the frontend application using the following command

ng serve

Console output:

✔ Browser application bundle generation complete.
Initial Chunk Files | Names         |      Size
vendor.js           | vendor        |   2.99 MB
scripts.js          | scripts       | 149.18 kB
styles.css          | styles        | 143.17 kB
polyfills.js        | polyfills     | 141.35 kB
main.js             | main          |  52.83 kB
runtime.js          | runtime       |   6.15 kB

                    | Initial Total |   3.47 MB

Build at: 2020-11-29T17:55:51.532Z - Hash: b5b9b31a131b1331acdd - Time: 9212ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
✔ Compiled successfully.

Hit http://localhost:4200 link in a browser that will host this Angular 10 CRUD app.



Download source code from our GitHub Repository




If you stuck while coding please use our comment section.✌Happy coding

More Topics:

Popular posts from this blog

Spring boot video streaming example-HTML5

DataTable-Pagination example with Spring boot, jQuery and ajax

10 Best Job Posting Sites 2021-2022

Spring boot web project free download:User Registration System

Spring Boot + JPA/Hibernate One to Many mapping example

5 Hardest Puzzle,100% fail answers

Java security AES,SHA256,SHA512,MD5-Spring Boot Project Free Download

Spring Boot-AngularJS-Bootstrap-JPA-CRUD

ReactJS - Bootstrap - Buttons

Spring Boot file upload/download example