Spring Boot + Angular + MongoDB CRUD example

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


-Add User:



-Retrieve all Users:



-User by ID:


-Update User:


We divided this tutorial into two parts. 

PART 1 - Restful API Development with Spring Boot 
PART 2 - UI development using Angular 10 

PART 1 - Restful API Development with Spring Boot 

These are APIs that Spring backend App will export:


Backend project structure



Maven[pom.xml]

A Project Object Model or POM is the fundamental unit of work in Maven. It is an XML file that contains information about the project and configuration details utilized by Maven to build the project.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath />
</parent>
<groupId>com.example</groupId>
<artifactId>springboot_angular_mongodb_crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_angular_mongodb_crud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>


application.properties

spring.data.mongodb.uri=mongodb://localhost:27017/user-dev



Create User Document

package com.knf.dev.document;

import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "database_sequences")
public class User {

private String _id;
private String firstName;
private String lastName;
private String emailId;
public String getId() {
return _id;
}
public void setId(String _id) {
this._id = _id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public User(String _id, String firstName,
String lastName, String emailId) {
super();
this._id = _id;
this.firstName = firstName;
this.lastName = lastName;
this.emailId = emailId;
}
public User() {
super();
}
}


Create a User Repository

package com.knf.dev.repository;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.knf.dev.document.User;

@Repository
public interface UserRepository
extends MongoRepository<User, String> {

}


Create User Controller

package com.knf.dev.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.knf.dev.ResourceNotFoundException.ResourceNotFoundException;
import com.knf.dev.document.User;
import com.knf.dev.repository.UserRepository;

@RestController
@CrossOrigin(origins = "http://localhost:4200")
@RequestMapping("/api/v1")
public class UserController {
@Autowired
private UserRepository userRepository;

@GetMapping("/users")
public List<User> getAllUsers() {

return userRepository.findAll();
}

@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById
(@PathVariable(value = "id")
String id) throws ResourceNotFoundException {

User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not found for this id :: " + id));
return ResponseEntity.ok().body(user);
}

@PostMapping("/users")
public User createUser(@Valid @RequestBody User user) {
return userRepository.save(user);
}

@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser
(@PathVariable(value = "id")
String id, @Valid @RequestBody User userDto)

throws ResourceNotFoundException {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not found for this id :: " + id));

user.setEmailId(userDto.getEmailId());
user.setLastName(userDto.getLastName());
user.setFirstName(userDto.getFirstName());
user.setId(id);
final User updateUser = userRepository.save(user);
return ResponseEntity.ok(updateUser);
}

@DeleteMapping("/users/{_id}")
public Map<String, Boolean> deleteUser
(@PathVariable(value = "_id")
String id) throws ResourceNotFoundException {

User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not found for this id :: " + id));

userRepository.delete(user);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}


Spring Boot Main Driver

package com.knf.dev;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootAngularMongodbCrudApplication {

public static void main(String[] args) {
SpringApplication.run
(SpringbootAngularMongodbCrudApplication.class, args);
}
}



PART 2 - UI development using Angular

Project Structure




The package.json

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 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.3",
"@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"
}
}


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 the src/app folder and add the following code to it -
export class User {
_id: string;
firstName: string;
lastName: string;
emailId: string;
active: boolean;

}


User List Component

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

Path - src/app/user-list/user-list.component.ts
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>


Add User Component

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

CreateUserComponent is used to create and handle 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 a User Template

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

Path - src/app/create-user/create-user.component.html 
<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 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 updated 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>


User Details Component

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

The UserDetailsComponent component is 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  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 + Spring Boot + 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));



Local Setup and Run the application

Step 1: Install Mongo DB - Click here


Step 2: Download or clone the source code from GitHub to a local machine - Click here



Backend


Step 3: mvn clean install


Step 4: Run the Spring Boot application - mvn spring-boot:run



Frontend


Step 5: npm install 


Step 6: ng serve

From the browser call the endpoint http://localhost:4200/

Comments

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

ReactJS - Bootstrap - Buttons

Java - How to Count the Number of Occurrences of Substring in a String

Spring Boot 3 + Spring Security 6 + Thymeleaf - Registration and Login Example

Spring Core | BeanFactoryPostProcessor | Example

Custom Exception Handling in Quarkus REST API

File Upload, Download, And Delete - Azure Blob Storage + Spring Boot Example

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024