Angular + Kotlin +Spring Boot + Mongo DB CRUD Example
Hello everyone, today we will learn how to develop a full-stack web application that is a basic User Management Application using Angular 10, Kotlin, Spring Boot, and MongoDB.You can download the source code from our GitHub Repository.
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
Following is the screenshot of our application -
A quick overview of Angular 10, Kotlin, Spring Boot, and MongoDB
Kotlin
Kotlin is a statically typed, general-purpose programming language targeting the Java platform. Kotlin is concise, safe, pragmatic, and fixated on interoperability with Java code. It can be used virtually everywhere Java is utilized today: for server-side development, Android apps, and much more. Kotlin works great with all subsisting Java libraries and frameworks and runs with the same level of performance as Java.
MongoDB
MongoDB is a document database built on a scale-out architecture that has to propagate with developers of all kinds who are building scalable applications utilizing agile methodologies.
MongoDB was built for people who are building internet and business applications who need to evolve expeditiously and scale elegantly. If you are doing that, you should consider MongoDB.
Companies and development teams of all sizes use MongoDB because:
- The document data model is a potent way to store and retrieve data that sanctions developers to move expeditiously.
- MongoDB’s horizontal, scale-out architecture can fortify astronomically immense volumes of both data and traffic.
- MongoDB has a great utilizer experience for developers who can install MongoDB and commence inscribing code immediately.
Angular
Angular is a TypeScript-predicated open-source web application framework led by the Angular Team at Google and by a community of individuals and corporations. Angular is a consummate re-indite from the same team that built AngularJS.
Spring Boot
Spring boot to develop REST web services and microservices. Spring Boot has taken the Spring framework to the next level. It has drastically reduced the configuration and setup time required for spring projects. We can set up a project with almost zero configuration and start building the things that actually matter to your application.
We divided this tutorial into two parts
PART 1 - Restful API Development with Kotlin + Spring Boot (back end)
PART 2 - UI development using Angular 10 (front end/client)
PART 1 - Restful API Development with Kotlin + Spring Boot
These are APIs that Spring backend App will export:
Backend project structure:
pom.xml[Maven]
Include spring-boot-starter-web for Spring MVC and REST structure, and spring-boot-starter-data-mongodb for CRUD repository.
<?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.3.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.knf.dev</groupId> <artifactId>springboot-kotlin-crud</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-kotlin-crud</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> <kotlin.version>1.3.72</kotlin.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>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
<build> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin </testSourceDirectory> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <configuration> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> </compilerPlugins> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> </plugin> </plugins> </build></project>
Creating the Document
import org.bson.types.ObjectIdimport org.springframework.data.annotation.Idimport org.springframework.data.mongodb.core.mapping.Documentimport org.springframework.web.bind.annotation.CrossOrigin
@Document(collection = "users")data class User( @Id var id: String? = ObjectId().toHexString(), val firstName: String, val lastName: String, val emailId: String)
Creating the Repository
Let’s now create the repository for accessing the data from the database.
package com.knf.dev.repository
import com.knf.dev.model.Userimport org.springframework.data.mongodb.repository.MongoRepositoryimport org.springframework.stereotype.Repository
@Repositoryinterface UserRepository : MongoRepository<User, String>
Creating the controller End-points
package com.knf.dev.controller
import com.knf.dev.model.Userimport com.knf.dev.repository.UserRepositoryimport org.springframework.http.HttpStatusimport org.springframework.http.ResponseEntityimport org.springframework.web.bind.annotation.*import java.util.*
@RestController@RequestMapping("/api/v1/")@CrossOrigin(origins = arrayOf("http://localhost:4200"))class UserController(private val userRepository: UserRepository) {
@GetMapping("/users") fun getAllUsers(): List<User> = userRepository.findAll()
@PostMapping("/users") fun createNewUser(@RequestBody user: User): User = userRepository.save(user)
@GetMapping("/users/{id}") fun getUserById(@PathVariable(value = "id") userId: String): ResponseEntity<User> { return userRepository.findById(userId).map { user -> ResponseEntity.ok(user) }.orElse(ResponseEntity.notFound().build()) }
@PutMapping("/users/{id}") fun updateUserById(@PathVariable(value = "id") userId: String, @RequestBody newUser: User): ResponseEntity<User> {
return userRepository.findById(userId).map { existingUser -> val updatedUser: User = existingUser .copy(firstName = newUser.firstName, lastName = newUser.lastName, emailId = newUser.emailId) ResponseEntity.ok().body(userRepository.save(updatedUser)) }.orElse(ResponseEntity.notFound().build())
}
@DeleteMapping("/users/{id}") fun deleteUserById(@PathVariable(value = "id") userId: String): ResponseEntity<Void> { return userRepository.findById(userId).map { user -> userRepository.delete(user) ResponseEntity<Void>(HttpStatus.OK) }.orElse(ResponseEntity.notFound().build())
}}
Spring Boot Main Driver
package com.knf.dev
import org.springframework.boot.autoconfigure.SpringBootApplicationimport org.springframework.boot.runApplication
@SpringBootApplicationclass SpringbootKotlinCrudApplication
fun main(args: Array<String>) { runApplication<SpringbootKotlinCrudApplication>(*args)}
PART 2 - UI development using Angular
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 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 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 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
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>
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 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 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 + 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));
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: npm serve
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: npm serve