Kotlin + Spring Boot + React JS + MongoDB CRUD example

Hello everyone, hope you are doing well, 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 


A quick overview of React, 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 utilised 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 and 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 matter to your application.


Full-Stack App Development 

We divided this tutorial into two parts 

PART 1 - Restful API Development with Kotlin, Spring Boot, MongoDB (back end) 
PART 2 - UI development using React JS(front end/client)


Project 1:Spring Boot-CRUD-Restful-API

Project Structure



pom.xml

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> 


Create the User Document 

@Document annotation helps us override the collection name by “users”.
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 User 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 User Controller

The @RestController annotation was introduced in Spring 4.0 to simplify the engendering of RESTful web services. It's a convenience annotation that combines @Controller and @ResponseBody.
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())

}
}


Spring Boot Main Driver

The Spring Boot application's main class contains a public static void main() method that starts up the Spring ApplicationContext.
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)
}



Project 2:React App, Front-End

Project Structure



package.json

It is used by the npm CLI (and yarn) to identify your project and understand how to handle the project's dependencies. It's the package. json file that enables npm to start your project, run scripts, install dependencies, publish to the NPM registry, and many other useful tasks.
{
"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


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


FooterComponent.js

import React, { Component } from 'react'

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

this.state = {

}
}

render() {
return (
<div>
<footer className="footer">
<span className="text-muted">
All Rights Reserved 2020 @Knowledgefactory.net
</span>
</footer>
</div>
)
}
}

export default FooterComponent


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()


index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'bootstrap/dist/css/bootstrap.min.css';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

serviceWorker.unregister();


index.css

body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas,
'Courier New',
monospace;
}


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>
</Switch>
</div>
<FooterComponent />
</Router>
</div>

);
}

export default App;


App.css

.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.footer {
position: absolute;
bottom: 0;
width:100%;
height: 50px;
background-color: black;
text-align: center;
color: white;
}


index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>



Local Setup and Run the application

Backend - Download source code - click here

Step 1: mvn clean install

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


Frontend - Download source code - click here

Step 3: npm install

Step 4: npm start

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


Popular posts from this blog

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

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

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

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete