Spring Boot + React Redux + MongoDb CRUD example
springboot
135
react redux
1
mongodb
33
CRUD
36
Male avatar

loveprogramming viết ngày 04/03/2021

https://grokonez.com/spring-framework/spring-data-rest/spring-boot-react-redux-mongodb-crud-example

In this tutorial, we will build React Redux Http Client & Spring Boot Server example that uses Spring Data to interact with MongoDB and React as a front-end technology to make request and receive response.

Technologies

  • Java 1.8
  • Maven 3.3.9
  • Spring Tool Suite 3.9.0.RELEASE
  • Spring Boot 2.0.1.RELEASE

  • Webpack 4.4.1

  • React 16.3.0

  • Redux 3.7.2

  • React Redux 5.0.7

  • axios 0.18.0

  • MongoDB 3.4.10

    Overview - Spring Boot + React Redux + MongoDb CRUD example

    react-redux-spring-boot-mongodb-crud-example-result-show-books

    1. Spring Boot Server

    react-redux-spring-boot-mongodb-crud-example-spring-server

Spring Data MongoDB - MongoRepository example:
How to use SpringData MongoRepository to interact with MongoDB

2. React Redux Client

react-redux-spring-boot-mongodb-crud-example-react-redux-client-architecture

For more details about:

  • Redux: A simple practical Redux example
  • Middleware: Middleware with Redux Thunk
  • Connecting React with Redux: How to connect React with Redux – react-redux example

    Project Structure - Spring Boot + React Redux + MongoDb CRUD example

    1. Spring Boot Server

    react-redux-spring-boot-mongodb-crud-example-spring-server-structure

  • Class Book corresponds to document in books collection.

  • BookMongoRepository is an interface extends MongoRepository, will be autowired in BookController for implementing repository methods and finder methods.

  • BookController is a REST Controller which has request mapping methods for RESTful requests such as: getAll, create, update, delete Books.

  • Configuration for Spring Data MongoDB properties in application.properties

  • Dependencies for Spring Boot and Spring Data MongoDB in pom.xml

    2. React Redux Client

    react-redux-spring-boot-mongodb-crud-example-react-redux-client-structure

  • AppRouter is for routing.

  • actions, reducers and store contains Redux elements.

  • components folder includes React Components with react-redux connect() function.

  • axios configures base URL for HTTP client. We use axios methods as async side-effects inside actions/books.js.

    SpringBoot Dependency

    pom.xml

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

Book - Data Model

model/Book.java


package com.javasampleapproach.reactmongodb.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "books")
public class Book {
    @Id
    private String id;

    private String title;
    private String description;
    private String author;
    private int published;

    public Book() {
    }

    public String getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public int getPublished() {
        return published;
    }

    public void setPublished(int published) {
        this.published = published;
    }

    @Override
    public String toString() {
        return "Book [id=" + id + ", title=" + title + ", description=" + description + ", author=" + author
                + ", published=" + published + "]";
    }

}

MongoDB Repository

repo/BookMongoRepository.java


package com.javasampleapproach.reactmongodb.repo;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.javasampleapproach.reactmongodb.model.Book;

public interface BookMongoRepository extends MongoRepository {

}

SpringBoot REST Controller

controller/BookController.java


package com.javasampleapproach.reactmongodb.controller;

import java.util.List;
import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.javasampleapproach.reactmongodb.model.Book;
import com.javasampleapproach.reactmongodb.repo.BookMongoRepository;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class BookController {

    @Autowired
    BookMongoRepository bookRepository;

    @GetMapping("/books")
    public List getAllBooks() {
        System.out.println("Get all Books...");

        return bookRepository.findAll();
    }

    @PostMapping("/books/create")
    public Book createBook(@Valid @RequestBody Book book) {
        System.out.println("Create Book: " + book.getTitle() + "...");

        return bookRepository.save(book);
    }

    @GetMapping("/books/{id}")
    public ResponseEntity getBook(@PathVariable("id") String id) {
        System.out.println("Get Book by id...");

        Optional bookData = bookRepository.findById(id);
        if (bookData.isPresent()) {
            return new ResponseEntity(bookData.get(), HttpStatus.OK);
        } else {
            return new ResponseEntity(HttpStatus.NOT_FOUND);
        }
    }

    @PutMapping("/books/{id}")
    public ResponseEntity updateBook(@PathVariable("id") String id, @RequestBody Book book) {
        System.out.println("Update Book with ID = " + id + "...");

        Optional bookData = bookRepository.findById(id);
        if (bookData.isPresent()) {
            Book savedBook = bookData.get();
            savedBook.setTitle(book.getTitle());
            savedBook.setAuthor(book.getAuthor());
            savedBook.setDescription(book.getDescription());
            savedBook.setPublished(book.getPublished());

            Book updatedBook = bookRepository.save(savedBook);
            return new ResponseEntity(updatedBook, HttpStatus.OK);
        } else {
            return new ResponseEntity(HttpStatus.NOT_FOUND);
        }
    }

    @DeleteMapping("/books/{id}")
    public ResponseEntity deleteBook(@PathVariable("id") String id) {
        System.out.println("Delete Book with ID = " + id + "...");

        try {
            bookRepository.deleteById(id);
        } catch (Exception e) {
            return new ResponseEntity("Fail to delete!", HttpStatus.EXPECTATION_FAILED);
        }

        return new ResponseEntity("Book has been deleted!", HttpStatus.OK);
    }

    @DeleteMapping("/books/delete")
    public ResponseEntity deleteAllBooks() {
        System.out.println("Delete All Books...");

        try {
            bookRepository.deleteAll();
        } catch (Exception e) {
            return new ResponseEntity("Fail to delete!", HttpStatus.EXPECTATION_FAILED);
        }

        return new ResponseEntity("All Books have been deleted!", HttpStatus.OK);
    }
}

Configuration for Spring Data MongoDB

application.properties


spring.data.mongodb.database=jsa_mongodb
spring.data.mongodb.port=27017

Dependency - React Redux Client

package.json


{
  "name": "react-redux-springboot",
  "version": "1.0.0",
  "main": "index.js",
  "author": "JavaSampleApproach",
  "license": "MIT",
  "scripts": {
    "serve": "live-server public",
    "build": "webpack",
    "dev-server": "webpack-dev-server"
  },
  "dependencies": {
    "babel-cli": "6.24.1",
    "babel-core": "6.25.0",
    "babel-loader": "7.1.4",
    "babel-plugin-transform-object-rest-spread": "6.26.0",
    "babel-preset-env": "1.6.1",
    "babel-preset-react": "6.24.1",
    "css-loader": "0.28.11",
    "node-sass": "4.8.3",
    "react": "16.3.0",
    "react-dom": "16.3.0",
    "react-modal": "3.3.2",
    "react-redux": "5.0.7",
    "react-router-dom": "4.2.2",
    "redux": "3.7.2",
    "sass-loader": "6.0.7",
    "style-loader": "0.20.3",
    "webpack": "4.4.1",
    "webpack-cli": "2.0.13",
    "webpack-dev-server": "3.1.1",
    "redux-thunk": "2.2.0",
    "axios":"0.18.0"
  }
}

.babelrc


{
    "presets": [
        "env",
        "react"
    ],
    "plugins": [
        "transform-object-rest-spread"
    ]
}

Run cmd: yarn install.

2.2 Configure base URL

axios/axios.js


import axios from 'axios';

export default axios.create({
    baseURL: 'http://localhost:8080/api'
});</code></pre>
<h2>Redux Action</h2>
<em>actions/books.js</em>

<pre><code class="language-java">
import axios from '../axios/axios';

const _addBook = (book) => ({
    type: 'ADD_BOOK',
    book
});

export const addBook = (bookData = {
    title: '',
    description: '',
    author: '',
    published: 0
}) => {
    return (dispatch) => {
        const book = {
            title: bookData.title,
            description: bookData.description,
            author: bookData.author,
            published: bookData.published
        };

        return axios.post('books/create', book).then(result => {
            dispatch(_addBook(result.data));
        });
    };
};

const _removeBook = ({ id } = {}) => ({
    type: 'REMOVE_BOOK',
    id
});

export const removeBook = ({ id } = {}) => {
    return (dispatch) => {
        return axios.delete(`books/${id}`).then(() => {
            dispatch(_removeBook({ id }));
        })
    }
};

const _editBook = (id, updates) => ({
    type: 'EDIT_BOOK',
    id,
    updates
});

export const editBook = (id, updates) => {
    return (dispatch) => {
        return axios.put(`books/${id}`, updates).then(() => {
            dispatch(_editBook(id, updates));
        });
    }
};

const _getBooks = (books) => ({
    type: 'GET_BOOKs',
    books
});

export const getBooks = () => {
    return (dispatch) => {
        return axios.get('books').then(result => {
            const books = [];

            result.data.forEach(item => {
                books.push(item);
            });

            dispatch(_getBooks(books));
        });
    };
};

Redux Reducer

reducers/books.js

const booksReducerDefaultState = [];

export default (state = booksReducerDefaultState, action) => {
    switch (action.type) {
        case 'ADD_BOOK':
            return [
                ...state,
                action.book
            ];
        case 'REMOVE_BOOK':
            return state.filter(({ id }) => id !== action.id);
        case 'EDIT_BOOK':
            return state.map((book) => {
                if (book.id === action.id) {
                    return {
                        ...book,
                        ...action.updates
                    };
                } else {
                    return book;
                }
            });
        case 'GET_BOOKs':
            return action.books;
        default:
            return state;
    }
};

Redux Store

store/store.js

import { createStore, applyMiddleware } from "redux";
import books from '../reducers/books';
import thunk from 'redux-thunk';

export default () => {
    return createStore(books, applyMiddleware(thunk));
};

React Components

components/Book.js

import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { removeBook } from '../actions/books';

const Book = ({ id, title, description, author, published, dispatch }) => (
    <div>
        <Link to={`/book/${id}`}>
            <h4>{title} ({published})</h4>
        </Link>
        <p>Author: {author}</p>
        {description && <p>{description}</p>}
        <button onClick={() => {
            dispatch(removeBook({ id }));
        }}>Remove</button>
    </div>
);

export default connect()(Book);

components/DashBoard.js

import React from 'react';
import BookList from './BookList';

const DashBoard = () => (
    <div className='container__list'>
        <BookList />
    </div>
);

export default DashBoard;

components/BookList.js

import React from 'react';
import { connect } from 'react-redux';
import Book from './Book';

const BookList = (props) => (
    <div>
        Book List:
        <ul>
            {props.books.map(book => {
                return (
                    <li key={book.id}>
                        <Book {...book} />
                    </li>
                );
            })}
        </ul>

    </div>
);

const mapStateToProps = (state) => {
    return {
        books: state
    };
}

export default connect(mapStateToProps)(BookList);

components/AddBook.js

import React from 'react';
import BookForm from './BookForm';
import { connect } from 'react-redux';
import { addBook } from '../actions/books';

const AddBook = (props) => (
    <div>
        <h3>Set Book information:</h3>
        <BookForm
            onSubmitBook={(book) => {
                props.dispatch(addBook(book));
                props.history.push('/');
            }}
        />
    </div>
);

export default connect()(AddBook);

components/EditBook.js

import React from 'react';
import BookForm from './BookForm';
import { connect } from 'react-redux';
import { editBook } from '../actions/books';

const EditBook = (props) => (
    <div className='container__box'>
        <BookForm
            book={props.book}
            onSubmitBook={(book) => {
                props.dispatch(editBook(props.book.id, book));
                props.history.push('/');
            }}
        />
    </div>
);

const mapStateToProps = (state, props) => {
    return {
        book: state.find((book) =>
            book.id === props.match.params.id)
    };
};

export default connect(mapStateToProps)(EditBook);

components/BookForm.js

import React from 'react';

export default class BookForm extends React.Component {
    constructor(props) {
        super(props);
        this.onTitleChange = this.onTitleChange.bind(this);
        this.onAuthorChange = this.onAuthorChange.bind(this);
        this.onDescriptionChange = this.onDescriptionChange.bind(this);
        this.onPublishedChange = this.onPublishedChange.bind(this);
        this.onSubmit = this.onSubmit.bind(this);

        this.state = {
            title: props.book ? props.book.title : '',
            author: props.book ? props.book.author : '',
            description: props.book ? props.book.description : '',
            published: props.book ? props.book.published : 0,

            error: ''
        };
    }

    onTitleChange(e) {
        const title = e.target.value;
        this.setState(() => ({ title: title }));
    }

    onAuthorChange(e) {
        const author = e.target.value;
        this.setState(() => ({ author: author }));
    }

    onDescriptionChange(e) {
        const description = e.target.value;
        this.setState(() => ({ description: description }));
    }

    onPublishedChange(e) {
        const published = parseInt(e.target.value);
        this.setState(() => ({ published: published }));
    }

    onSubmit(e) {
        e.preventDefault();

        if (!this.state.title || !this.state.author || !this.state.published) {
            this.setState(() => ({ error: 'Please set title & author & published!' }));
        } else {
            this.setState(() => ({ error: '' }));
            this.props.onSubmitBook(
                {
                    title: this.state.title,
                    author: this.state.author,
                    description: this.state.description,
                    published: this.state.published
                }
            );
        }
    }

    render() {
        return (
            <div>
                {this.state.error && <p className='error'>{this.state.error}</p>}
                <form onSubmit={this.onSubmit} className='add-book-form'>

                    <input type="text" placeholder="title" autoFocus
                        value={this.state.title}
                        onChange={this.onTitleChange} />
                    <br />

                    <input type="text" placeholder="author"
                        value={this.state.author}
                        onChange={this.onAuthorChange} />
                    <br />

                    <textarea placeholder="description"
                        value={this.state.description}
                        onChange={this.onDescriptionChange} />
                    <br />

                    <input type="number" placeholder="published"
                        value={this.state.published}
                        onChange={this.onPublishedChange} />
                    <br />
                    <button>Add Book</button>
                </form>
            </div>
        );
    }
}

React Router

routers/AppRouter.js

import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Header from '../components/Header';
import DashBoard from '../components/DashBoard';
import AddBook from '../components/AddBook';
import EditBook from '../components/EditBook';
import NotFound from '../components/NotFound';

const AppRouter = () => (
    <BrowserRouter>
        <div className='container'>
            <Header />
            <Switch>
                <Route path="/" component={DashBoard} exact={true} />
                <Route path="/add" component={AddBook} />
                <Route path="/book/:id" component={EditBook} />
                <Route component={NotFound} />
            </Switch>
        </div>
    </BrowserRouter>
);

export default AppRouter;

components/Header.js

import React from 'react';
import { NavLink } from 'react-router-dom';

const Header = () => (
    <header>
        <h2>Java Sample Approach</h2>
        <h4>Book Mangement Application</h4>
        <div className='header__nav'>
            <NavLink to='/' activeClassName='activeNav' exact={true}>Dashboard</NavLink>
            <NavLink to='/add' activeClassName='activeNav'>Add Book</NavLink>
        </div>
    </header>
);

export default Header;

Reactjs Render App

app.js

import React from 'react';
import ReactDOM from 'react-dom';
import AppRouter from './routers/AppRouter';
import getAppStore from './store/store';
import { getBooks } from './actions/books';
import './styles/styles.scss';

import { Provider } from 'react-redux';

const store = getAppStore();

const template = (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

store.dispatch(getBooks()).then(() => {
    ReactDOM.render(template, document.getElementById('app'));
});

Run & Check Result

  • Build and Run Spring Boot project with commandlines: mvn clean install and mvn spring-boot:run.
  • Run the React App with command: yarn run dev-server.

  • Open browser for url http://localhost:8081/:
    Add Book:

react-redux-spring-boot-mongodb-crud-example-result-add-book

Show Books:

react-redux-spring-boot-mongodb-crud-example-result-show-books

Check MongoDB database:

react-redux-spring-boot-mongodb-crud-example-result-show-books-mongodb

Click on a Book's title, app goes to Edit Page:

react-redux-spring-boot-mongodb-crud-example-result-edit-book

Click Add Book button and check new Book list:

react-redux-spring-boot-mongodb-crud-example-result-edit-book-return

Click on certain Remove button to remove certain Book.
For example, removing Origin:

react-redux-spring-boot-mongodb-crud-example-result-remove-book

Check MongoDb Database:

react-redux-spring-boot-mongodb-crud-example-result-show-update-books-mongodb

Source Code

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

Male avatar

loveprogramming

444 bài viết.
77 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
Male avatar
1 0
Tutorial Link: (Link) (Ảnh) Django is a Pythonbased free and opensource web framework that follows the modeltemplateview architectural pattern. A...
loveprogramming viết 6 tháng trước
1 0
Male avatar
1 0
https://loizenai.com/angular11nodejspostgresqlcrudexample/ Angular 11 Node.js PostgreSQL Crud Example (Ảnh) Tutorial: “Angular 11 Node.js Postg...
loveprogramming viết 5 tháng trước
1 0
Male avatar
1 0
Angular Spring Boot jwt Authentication Example Github https://loizenai.com/angularspringbootjwt/ (Ảnh) Tutorial: ” Angular Spring Boot jwt Authe...
loveprogramming viết 5 tháng trước
1 0
Bài viết liên quan
White
45 7
Giới thiệu MongoDB là một giải pháp nosql database. Data được lưu ở dạng các bson document. Hỗ trợ vertical scaling và horizontal scaling, dynamic...
manhdung viết gần 6 năm trước
45 7
White
26 11
Quá trình lột xác ngoạn mục của một hệ thống cổ lỗ sĩ khi được thiết kế cẩn thận: 1 usecase thành công của việc áp dụng triệt để các phương pháp xử...
Minh Monmen viết 11 tháng trước
26 11
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


Male avatar
{{userFollowed ? 'Following' : 'Follow'}}
444 bài viết.
77 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!