Kotlin + Spring Boot + React JS + MongoDB CRUD - Full stack development foundation

Hello everyone, today we will learn how to develop a full-stack web application that is a basic User Management Application using React JS, 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 React, Kotlin, Spring Boot, and MongoDB 

 Kotlin

It’s an incipient 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.
React.js

React.js is an open-source JavaScript library that is used for building user interfaces specifically for single-page applications. React allows developers to create large web applications that can change data, without reloading the page. The main purpose of React is to be fast, scalable, and simple. It works only on user interfaces in the application.

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.

Full-Stack App Development 


We will build two projects: 
1. sprint boot-backend – To develop REST API 
2. react-frontend  – Consume REST API

Project 1:Spring Boot-CRUD-Restful-API

Project Structure

pom.xml

Include spring-boot-starter-web for Spring MVC and REST structure, 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

package com.knf.dev.model

import org.bson.types.ObjectId
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import 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.User
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository

@Repository
interface UserRepository : MongoRepository<User, String>


Creating the controller End-points

package com.knf.dev.controller


import com.knf.dev.model.User
import com.knf.dev.repository.UserRepository
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.util.*


@RestController
@RequestMapping("/api/v1/")
@CrossOrigin(origins = arrayOf("http://localhost:3000"))
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())

}
}


Main Class

package com.knf.dev

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SpringbootKotlinCrudApplication

fun main(args: Array<String>) {
runApplication<SpringbootKotlinCrudApplication>(*args)
}

Back End-Download the source code-click here

Run 

$ mvn spring-boot:run

Project 2:React App, Front-End

Project Structure


package.json

{
"name": "react-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.19.2",
"bootstrap": "^4.5.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

CreateUserComponent.jsx

import React, { Component } from 'react'
import UserService from '../services/UserService';

class CreateUserComponent extends Component {
constructor(props) {
super(props)

this.state = {
// step 2
id: this.props.match.params.id,
firstName: '',
lastName: '',
emailId: ''
}
this.changeFirstNameHandler = this.changeFirstNameHandler.bind(this);
this.changeLastNameHandler = this.changeLastNameHandler.bind(this);
this.saveOrUpdateUser = this.saveOrUpdateUser.bind(this);
}

// step 3
componentDidMount(){

// step 4
if(this.state.id === '_add'){
return
}else{
UserService.getUserById(this.state.id).then( (res) =>{
let user = res.data;
this.setState({firstName: user.firstName,
lastName: user.lastName,
emailId : user.emailId
});
});
}
}
saveOrUpdateUser = (e) => {
e.preventDefault();
let user = {firstName: this.state.firstName, lastName:
this.state.lastName, emailId: this.state.emailId};
console.log('user => ' + JSON.stringify(user));

// step 5
if(this.state.id === '_add'){
UserService.createUser(user).then(res =>{
this.props.history.push('/users');
});
}else{
UserService.updateUser(user, this.state.id).then( res => {
this.props.history.push('/users');
});
}
}
changeFirstNameHandler= (event) => {
this.setState({firstName: event.target.value});
}

changeLastNameHandler= (event) => {
this.setState({lastName: event.target.value});
}

changeEmailHandler= (event) => {
this.setState({emailId: event.target.value});
}

cancel(){
this.props.history.push('/users');
}

getTitle(){
if(this.state.id === '_add'){
return <h3 className="text-center">Add User</h3>
}else{
return <h3 className="text-center">Update User</h3>
}
}
render() {
return (
<div>
<br></br>
<div className = "container">
<div className = "row">
<div className = "card col-md-6 offset-md-3
offset-md-3">
{
this.getTitle()
}
<div className = "card-body">
<form>
<div className = "form-group">
<label> First Name: </label>
<input placeholder="First Name"
name="firstName" className="form-control"
value={this.state.firstName}
onChange={this.changeFirstNameHandler}/>
</div>
<div className = "form-group">
<label> Last Name: </label>
<input placeholder="Last Name"
name="lastName" className="form-control"
value={this.state.lastName}
onChange={this.changeLastNameHandler}/>
</div>
<div className = "form-group">
<label> Email Id: </label>
<input placeholder="Email Address"
name="emailId" className="form-control"
value={this.state.emailId}
onChange={this.changeEmailHandler}/>
</div>

<button className="btn btn-success"
onClick={this.saveOrUpdateUser}>Save</button>
<button className="btn btn-danger"
onClick={this.cancel.bind(this)} style={{marginLeft: "10px"}}>Cancel</button>
</form>
</div>
</div>
</div>

</div>
</div>
)
}
}

export default CreateUserComponent


ListUserComponent.jsx

import React, { Component } from 'react'
import UserService from '../services/UserService'

class ListUserComponent extends Component {
constructor(props) {
super(props)

this.state = {
users: []
}
this.addUser = this.addUser.bind(this);
this.editUser = this.editUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
}

deleteUser(id){
UserService.deleteUser(id).then( res => {
this.setState({users: this.state.users.filter(user =>
user.id !== id)});
});
}
viewUser(id){
this.props.history.push(`/view-user/${id}`);
}
editUser(id){
this.props.history.push(`/add-user/${id}`);
}

componentDidMount(){
UserService.getUsers().then((res) => {
this.setState({ users: res.data});
});
}

addUser(){
this.props.history.push('/add-user/_add');
}

render() {
return (
<div>
<h2 className="text-center">Users List</h2>
<div className = "row">
<button className="btn btn-primary" onClick={this.addUser}>
Add User</button>
</div>
<br></br>
<div className = "row">
<table className = "table table-striped
table-bordered">

<thead>
<tr>
<th> User First Name</th>
<th> User Last Name</th>
<th> User Email Id</th>
<th> Actions</th>
</tr>
</thead>
<tbody>
{
this.state.users.map(
user =>
<tr key = {user.id}>
<td> { user.firstName} </td>
<td> {user.lastName}</td>
<td> {user.emailId}</td>
<td>
<button onClick={ () =>
this.editUser(user.id)} className="btn btn-info">Update </button>
<button style={{marginLeft:
"10px"}} onClick={ () => this.deleteUser(user.id)} className="btn btn-danger">
Delete </button>
<button style={{marginLeft:
"10px"}} onClick={ () => this.viewUser(user.id)} className="btn btn-info">
View </button>
</td>
</tr>
)
}
</tbody>
</table>

</div>

</div>
)
}
}

export default ListUserComponent


UpdateUserComponent.jsx

import React, { Component } from 'react'
import UserService from '../services/UserService';

class UpdateUserComponent extends Component {
constructor(props) {
super(props)

this.state = {
id: this.props.match.params.id,
firstName: '',
lastName: '',
emailId: ''
}
this.changeFirstNameHandler = this.changeFirstNameHandler.bind(this);
this.changeLastNameHandler = this.changeLastNameHandler.bind(this);
this.updateUser = this.updateUser.bind(this);
}

componentDidMount(){
UserService.getUserById(this.state.id).then( (res) =>{
let user = res.data;
this.setState({firstName: user.firstName,
lastName: user.lastName,
emailId : user.emailId
});
});
}

updateUser = (e) => {
e.preventDefault();
let user = {firstName: this.state.firstName, lastName:
this.state.lastName, emailId: this.state.emailId};
console.log('user => ' + JSON.stringify(user));
console.log('id => ' + JSON.stringify(this.state.id));
UserService.updateUser(user, this.state.id).then( res => {
this.props.history.push('/users');
});
}
changeFirstNameHandler= (event) => {
this.setState({firstName: event.target.value});
}

changeLastNameHandler= (event) => {
this.setState({lastName: event.target.value});
}

changeEmailHandler= (event) => {
this.setState({emailId: event.target.value});
}

cancel(){
this.props.history.push('/users');
}

render() {
return (
<div>
<br></br>
<div className = "container">
<div className = "row">
<div className = "card col-md-6 offset-md-3 offset
-md-3">
<h3 className="text-center">Update User</h3>
<div className = "card-body">
<form>
<div className = "form-group">
<label> First Name: </label>
<input placeholder="First Name"
name="firstName" className="form-control"
value={this.state.firstName}
onChange={this.changeFirstNameHandler}/>
</div>
<div className = "form-group">
<label> Last Name: </label>
<input placeholder="Last Name"
name="lastName" className="form-control"
value={this.state.lastName}
onChange={this.changeLastNameHandler}/>
</div>
<div className = "form-group">
<label> Email Id: </label>
<input placeholder="Email Address"
name="emailId" className="form-control"
value={this.state.emailId}
onChange={this.changeEmailHandler}/>
</div>

<button className="btn btn-success"
onClick={this.updateUser}>Save</button>
<button className="btn btn-danger"
onClick={this.cancel.bind(this)} style={{marginLeft: "10px"}}>Cancel</button>
</form>
</div>
</div>
</div>

</div>
</div>
)
}
}

export default UpdateUserComponent


ViewUserComponent.jsx

import React, { Component } from 'react'
import UserService from '../services/UserService'

class ViewUserComponent extends Component {
constructor(props) {
super(props)

this.state = {
id: this.props.match.params.id,
user: {}
}
}

componentDidMount(){
UserService.getUserById(this.state.id).then( res => {
this.setState({user: res.data});
})
}

render() {
return (
<div>
<br></br>
<div className = "card col-md-6 offset-md-3">
<h3 className = "text-center"> View User Details</h3>
<div className = "card-body">
<div className = "row">
<label> User First Name: </label>
<div> { this.state.user.firstName }</div>
</div>
<div className = "row">
<label> User Last Name: </label>
<div> { this.state.user.lastName }</div>
</div>
<div className = "row">
<label> User Email ID: </label>
<div> { this.state.user.emailId }</div>
</div>
</div>

</div>
</div>
)
}
}

export default ViewUserComponent

UserService.js

import axios from 'axios';

const USER_API_BASE_URL = "http://localhost:8080/api/v1/users";

class UserService {

getUsers(){
return axios.get(USER_API_BASE_URL);
}

createUser(user){
return axios.post(USER_API_BASE_URL, user);
}

getUserById(userId){
return axios.get(USER_API_BASE_URL + '/' + userId);
}

updateUser(user, userId){
return axios.put(USER_API_BASE_URL + '/' + userId, user);
}

deleteUser(userId){
return axios.delete(USER_API_BASE_URL + '/' + userId);
}
}

export default new UserService()

HeaderComponent.js

import React, { Component } from 'react'
import'bootstrap/dist/css/bootstrap.min.css';
class HeaderComponent extends Component {
constructor(props) {
super(props)

this.state = {
}
}

render() {
return (
<div>
<header>
<nav className="navbar navbar-dark bg-primary">
<div><a href="knowledgefactory.net"
className="navbar-brand">User Management App</a></div>
</nav>
</header>
</div>
)
}
}

export default HeaderComponent

App.js

import React from 'react';
import logo from './logo.svg';
import './App.css';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
import ListUserComponent from './components/ListUserComponent';
import HeaderComponent from './components/HeaderComponent';
import FooterComponent from './components/FooterComponent';
import CreateUserComponent from './components/CreateUserComponent';
import UpdateUserComponent from './components/UpdateUserComponent';
import ViewUserComponent from './components/ViewUserComponent';

function App() {
return (
<div>
<Router>
<HeaderComponent />
<div className="container">
<Switch>
<Route path = "/" exact component =
{ListUserComponent}></Route>
<Route path = "/users" component =
{ListUserComponent}></Route>
<Route path = "/add-user/:id" component =
{CreateUserComponent}></Route>
<Route path = "/view-user/:id" component =
{ViewUserComponent}></Route>
{/* <Route path = "/update-user/:id" component =
{UpdateUserComponent}></Route> */}
</Switch>
</div>
<FooterComponent />
</Router>
</div>
);
}

export default App;


Download the package and its dependencies:

npm install

Run the application:  
npm start

Comments