Micronaut + React JS + 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 Micronaut, React 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 


Add User:


View All Users:


Update User:


View User:



We divided this tutorial into two parts. 

PART 1 - Rest APIs Development using Micronaut
PART 2 - UI development using React.js

 PART 1 - Rest APIs Development using Micronaut 

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.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.knf.dev.demo</groupId>
<artifactId>micronaut-mongodb-crud</artifactId>
<version>0.1</version>
<packaging>${packaging}</packaging>

<parent>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-parent</artifactId>
<version>3.1.1</version>
</parent>

<properties>
<packaging>jar</packaging>
<jdk.version>11</jdk.version>
<release.version>11</release.version>
<micronaut.version>3.1.1</micronaut.version>
<exec.mainClass>com.knf.dev.demo.Application
</exec.mainClass>
<micronaut.runtime>netty</micronaut.runtime>
</properties>

<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-server-netty</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-client</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-runtime</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.mongodb</groupId>
<artifactId>micronaut-mongo-sync</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micronaut.test</groupId>
<artifactId>micronaut-test-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.micronaut.build</groupId>
<artifactId>micronaut-maven-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
<version>${micronaut.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.group=com.knf.dev.demo</arg>
<arg>-Amicronaut.processing.module=micronaut-mongodb-crud</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>


application.yaml

micronaut:
application:
name: micronautMongodbCrud
mongodb.uri: mongodb://localhost:27017

micronaut:
server:
cors:
enabled: true
configurations:
web:
allowedOrigins:
- http://localhost:3000


Create Document - User.java

package com.knf.dev.demo.entity;

import io.micronaut.core.annotation.Introspected;

@Introspected
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 UserRepository.java

package com.knf.dev.demo.repository.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import com.knf.dev.demo.entity.User;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.Filters;
import jakarta.inject.Singleton;

@Singleton
public class UserRepository {
private final MongoClient mongoClient;

public UserRepository(MongoClient mongoClient) {
this.mongoClient = mongoClient;
}

private MongoCollection<Document> getCollection() {

return mongoClient.getDatabase("user-test").
getCollection("user-test");
}

public User saveUser(User user) {
Document document = new Document().
append("firstName", user.getFirstName())
.append("lastName", user.getLastName()).
append("emailId", user.getEmailId());
getCollection().insertOne(document);
user.setId(document.getObjectId("_id").toString());
return user;
}

public User updateUser(String id, User user) {
Optional<Document> optionalDocument = findDocumentById(id);
if (optionalDocument.isPresent()) {
Document document = optionalDocument.get();
document.put("firstName", user.getFirstName());
document.put("lastName", user.getLastName());
document.put("emailId", user.getEmailId());
Bson bsonFilter = Filters.eq("_id", new ObjectId(id));
getCollection().replaceOne(bsonFilter, document);
}
return user;
}

public List<User> findAll() {
List<User> users = new ArrayList<>();
MongoCursor<Document> cursor = getCollection().
find().iterator();

try {
while (cursor.hasNext()) {
Document document = cursor.next();
User user = new User();
user.setLastName(document.getString("lastName"));
user.setFirstName(document.getString("firstName"));
user.setEmailId(document.getString("emailId"));
user.setId(String.
valueOf(document.getObjectId("_id")));
users.add(user);
}
} finally {
cursor.close();
}
return users;
}

public Optional<User> findUserById(String id) {
return findDocumentById(id).map(this::mapUser);
}

public void deleteUserById(String id) {
Bson bsonFilter = Filters.eq("_id", new ObjectId(id));
getCollection().deleteOne(bsonFilter);
}

private Optional<Document> findDocumentById(String id) {
Bson bsonFilter = Filters.eq("_id", new ObjectId(id));
try (MongoCursor<Document> iterator = getCollection().
find(bsonFilter).iterator()) {
return iterator.hasNext() ? Optional.
of(iterator.next()) : Optional.empty();
}
}

private User mapUser(Document document) {
User user = new User();
user.setId(document.getObjectId("_id").toString());
user.setEmailId(document.getString("emailId"));
user.setLastName(document.getString("lastName"));
user.setFirstName(document.getString("firstName"));
return user;
}
}


Create UserController.java

package com.knf.dev.demo.contoller;

import java.util.List;
import java.util.Optional;
import com.knf.dev.demo.entity.User;
import com.knf.dev.demo.repository.impl.UserRepository;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Delete;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Put;
import jakarta.inject.Inject;

@Controller("/api/v1/users")
public class UserController {

@Inject
UserRepository userRepository;

@Get
public List<User> getUsers() {
return userRepository.findAll();
}

@Get("/{id}")
public Optional<User> getUser(String id) {
return userRepository.findUserById(id);
}

@Post
public HttpResponse<User> addUser(@Body User user) {
userRepository.saveUser(user);
return HttpResponse.created(user);
}

@Put("/{id}")
public HttpResponse<User> updateUser(String id,
@Body User user) {

userRepository.updateUser(id, user);
return HttpResponse.created(user);
}

@Delete("/{id}")
public HttpResponse<Void> delete(String id) {
userRepository.deleteUserById(id);
return HttpResponse.noContent();
}
}


Create Main Class

package com.knf.dev.demo;

import io.micronaut.runtime.Micronaut;

public class Application {

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


Run the backend application

mvn mn:run or run as java application


PART 2 - UI Development using React.js

Package structure - Front end



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

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 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;

Download Source code - Local environment setup

Step1: Install JDK 17 - click here


Step2: Install eclipse - click here


Step3: Install ReactJS - click here


Step4: Install VS code - click here


Step5: Download/clone source code - click here


Step6: Install Apache Maven - click here


Step7: Install MongoDB - click here



Backend setup


Step8: Execute the command - mvn clean install


Step9: Run the app as a Java application or execute  mvn mn:run



Frontend setup


Step10: The npm install installs all modules that are listed on package.json file and their dependencies - npm install


Step11: Run the Frontend application - npm start

Comments

Popular posts from this blog

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

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

Java - DES Encryption and Decryption example

Java - Blowfish Encryption and decryption Example

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

ReactJS - Bootstrap - Buttons

Top 5 Java ORM tools - 2024

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

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

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