Quarkus + 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 Quarkus, Angular 10 and MongoDB.
GitHub repository link is provided at the end of this tutorial. You can download the source code.
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic User Management Application with CRUD features:
• Create User
• List User
• Update User
• Delete User
User Interface
-Add User:
-View all Users:
-View User by ID:
-Update User:
We divided this tutorial into two parts.
PART 1 - Rest APIs Development using Quarkus and MongoDB
PART 2 - UI development using Angular
PART 1 - Rest APIs Development using Quarkus
Project Directory:
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. It contains default values for most projects. Some of the configurations that can be designated in the POM is the project dependencies, the plugins or goals that can be executed, the build profiles, and so on. Other information such as the project version, description, developers, mailing lists and such can withal be designated.
<?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.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.knf.dev</groupId><artifactId>quarkus-mongo-panache-crud</artifactId><version>1.0</version><properties><compiler-plugin.version>3.8.1</compiler-plugin.version><maven.compiler.parameters>true</maven.compiler.parameters><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><quarkus-plugin.version>2.2.3.Final</quarkus-plugin.version><quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id><quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id><quarkus.platform.version>2.2.3.Final</quarkus.platform.version><surefire-plugin.version>2.22.1</surefire-plugin.version></properties><dependencyManagement><dependencies><dependency><groupId>${quarkus.platform.group-id}</groupId><artifactId>${quarkus.platform.artifact-id}</artifactId><version>${quarkus.platform.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>io.quarkus</groupId><artifactId>quarkus-resteasy-jsonb</artifactId></dependency><dependency><groupId>io.quarkus</groupId><artifactId>quarkus-mongodb-panache</artifactId></dependency><dependency><groupId>io.quarkus</groupId><artifactId>quarkus-junit5</artifactId><scope>test</scope></dependency><dependency><groupId>io.rest-assured</groupId><artifactId>rest-assured</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>io.quarkus</groupId><artifactId>quarkus-maven-plugin</artifactId><version>${quarkus-plugin.version}</version><executions><execution><goals><goal>build</goal></goals></execution></executions></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>${compiler-plugin.version}</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>${surefire-plugin.version}</version><configuration><systemProperties><java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager></systemProperties></configuration></plugin></plugins></build><profiles><profile><id>native</id><activation><property><name>native</name></property></activation><build><plugins><plugin><artifactId>maven-failsafe-plugin</artifactId><version>${surefire-plugin.version}</version><executions><execution><goals><goal>integration-test</goal><goal>verify</goal></goals><configuration><systemProperties><native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path></systemProperties></configuration></execution></executions></plugin></plugins></build><properties><quarkus.package.type>native</quarkus.package.type></properties></profile></profiles></project>
application.properties
quarkus.mongodb.connection-string = mongodb://localhost:27017
quarkus.mongodb.database = test-user-data
quarkus.http.cors=true
quarkus.http.cors.origins=http://localhost:4200
quarkus.http.cors.methods=GET,PUT,POST,DELETE
Create Document - User.java
package com.knf.dev.model;import io.quarkus.mongodb.panache.PanacheMongoEntity;import org.bson.types.ObjectId;public class User {public ObjectId id;private String firstName;private String lastName;private String emailId;public ObjectId getId() {return id;}public void setId(ObjectId 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(ObjectId id, String firstName,String lastName, String emailId) {this.id = id;this.firstName = firstName;this.lastName = lastName;this.emailId = emailId;}public User() {super();}}
Create UserRepository.java
@ApplicationScoped object is created once for the duration of the application.it can be used multiple times in the application once created.package com.knf.dev.repository;import com.knf.dev.model.User;import io.quarkus.mongodb.panache.PanacheMongoRepository;import javax.enterprise.context.ApplicationScoped;@ApplicationScopedpublic class UserRepository implements PanacheMongoRepository<User> {}
Create UserController.java
package com.knf.dev.controller;import com.knf.dev.model.User;import com.knf.dev.repository.UserRepository;import io.quarkus.mongodb.panache.PanacheQuery;import org.bson.types.ObjectId;import javax.inject.Inject;import javax.ws.rs.*;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Response;import java.util.List;@Path("/api/users")public class UserController {private final UserRepository userRepository;@Injectpublic UserController(UserRepository userRepository) {this.userRepository = userRepository;}@GETpublic List<User> getUsers() {return userRepository.listAll();}@GET@Path("/{id}")@Produces(MediaType.APPLICATION_JSON)public User getUser(@PathParam("id") String id) {return userRepository.findById(new ObjectId(id));}@PUT@Path("/{id}")@Consumes(MediaType.APPLICATION_JSON)@Produces(MediaType.APPLICATION_JSON)public void updateUser(@PathParam("id") String id, User user) {user.setId(new ObjectId(id));userRepository.update(user);}@POSTpublic Response addUser(User user) {userRepository.persist(user);return Response.status(Response.Status.CREATED).build();}@DELETE@Path("/{id}")public void deleteUser(@PathParam("id") String id) {User user = userRepository.findById(new ObjectId(id));userRepository.delete(user);}}
PART 2 - UI Development using Angular
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 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.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 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
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]);
}
}Create User List Template and Component
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">Create User List Template and Component
<thead>Create User List Template and Component
<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
CreateUserComponent is used to create and handle new user form data. Add the following code to it -
Path - src/app/create-user/create-user.component.ts
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 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 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>
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 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:8080/api/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 = 'Quarkus + Angular 10 + 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
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));
Frontend local environment setup
Step 1: Download the dependencies using the following command
npm install
Step 2: Run the frontend application using the following command
ng serve
Hit http://localhost:4200 link in a browser that will host this Angular 10 CRUD app.