How to Integrate React Redux + Nodejs/Express RestAPIs + Mongoose ODM – MongoDB CRUD example

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---feature-image

How to Integrate React Redux + Nodejs/Express RestAPIs + Mongoose ODM – MongoDB CRUD example

In this tutorial, we will build React Redux Http Client & Nodejs/Express RestAPIs Server example that uses Mongoose ODM to interact with MongoDB database and React as a front-end technology to make request and receive response.

Here is an example of a CRUD (Create, Read, Update, Delete) application built with React, Node.js, and MongoDB:

1. Setting up the backend

First, you will need to set up the backend of your application using Node.js and MongoDB. Here’s how you can do this:

Install Node.js and MongoDB on your system.
Create a new Node.js project and install the required dependencies: express, mongodb, and body-parser.
Connect to the MongoDB database using the MongoClient from the mongodb library.
Create a simple Express server that listens for HTTP requests and handles them using appropriate routing and middleware functions.
Create the CRUD routes for your API. For example, you might have routes like /api/items for creating, reading, updating, and deleting items in the database.
Here’s an example of how the server code might look:

const express = require('express');
const mongodb = require('mongodb');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

// Parse incoming request bodies in a middleware before your handlers
app.use(bodyParser.json());

// Connect to the MongoDB database
mongodb.MongoClient.connect('mongodb://localhost:27017', (err, client) => {
  if (err) {
    console.log(err);
    process.exit(1);
  }

  // Save the client reference to reuse the connection
  const db = client.db('mydatabase');

  // Create the CRUD routes for the /items resource
  app.get('/api/items', (req, res) => {
    // Read all items from the database
    db.collection('items').find().toArray((err, items) => {
      if (err) {
        console.log(err);
        res.sendStatus(500);
      } else {
        res.send(items);
      }
    });
  });

  app.post('/api/items', (req, res) => {
    // Create a new item in the database
    db.collection('items').insertOne(req.body, (err, result) => {
      if (err) {
        console.log(err);
        res.sendStatus(500);
      } else {
        res.send(result.ops[0]);
      }
    });
  });

  app.put('/api/items/:id', (req, res) => {
    // Update an existing item in the database
    db.collection('items').updateOne({ _id: mongodb.ObjectId(req.params.id) }, { $set: req.body }, (err, result) => {
      if (err) {
        console.log(err);
        res.sendStatus(500);
      } else {
        res.sendStatus(204);
      }
    });
  });

  app.delete('/api/items/:id', (req, res) => {
    // Delete an item from the database
    db.collection('items').deleteOne({ _id: mongodb.ObjectId(req.params.id) }, (err, result) => {

Related posts:
Crud RestAPIs with NodeJS/Express, MongoDB using Mongoose
How to connect React with Redux – react-redux example

Technologies

– Webpack 4.4.1
– React 16.3.0
– Redux 3.7.2
– React Redux 5.0.7
– axios 0.18.0

– Node.js/Express
– Mongoose ODM

– MongoDB

Overview

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---overview-1

1. Nodejs/Express Server

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---backend-architecture

2. React Redux Client

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---react-redux-client

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

Practice

1. Node.js Backend

– Project structure:

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---nodejs-project-structure

Setting up Nodejs/Express project

Init package.json by cmd:

npm init

Install express, mongoose & cors:

$npm install express cors mongoose --save

-> now package.json file:

{
  "name": "node.js-react-restapis",
  "version": "1.0.0",
  "description": "Node.js RestAPIs access MongoDB by Mongoose",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Nodejs",
    "RestAPIs",
    "Express",
    "MongoDB",
    "Mongoose"
  ],
  "author": "ozenero.com",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.16.4",
    "mongoose": "^5.4.2"
  }
}

Setting up Mongoose connection

./app/config/mongodb.config.js file:

module.exports = {
    url: 'mongodb://localhost:27017/nodejs-demo'
}

Create Mongoose model

./app/model/book.model.js file:

const mongoose = require('mongoose');
var Schema = mongoose.Schema;
 
var bookSchema = new Schema({
    title: String,
    author: String,
	description: String,
	published: String	
});

bookSchema.method('toClient', function() {
    var obj = this.toObject();

    //Rename fields
    obj.id = obj._id;
    delete obj._id;

    return obj;
});

module.exports = mongoose.model('Book', bookSchema); 

Express RestAPIs

Route

-> Define Book’s routes in ./app/route/book.route.js file:

module.exports = function(app) {
 
    const books = require('../controller/book.controller.js');
 
    // Create a new Book
    app.post('/api/books/create', books.create);
 
    // Retrieve all Books
    app.get('/api/books', books.findAll);
 
    // Retrieve a single Book by Id
    app.get('/api/books/:bookId', books.findOne);
	 
    // Update a Book with Id
    app.put('/api/books/:bookId', books.update);
 
    // Delete a Book with Id
    app.delete('/api/books/:bookId', books.delete);
}

Controller

-> Implement Book’s controller in ./app/controller/book.controller.js file:

const Book = require('../model/book.model.js');

// POST a Book
exports.create = (req, res) => {
    // Create a Book
    const book = new Book({
		title: req.body.title,
		author: req.body.author,
		description: req.body.description,
		published: req.body.published
    });
 
    // Save a Book into MongoDB
    book.save()
    .then(book => {
        res.send(book.toClient());
    }).catch(err => {
        res.status(500).send({
            message: err.message
        });
    });
};
 
// FETCH all Books
exports.findAll = (req, res) => {
    Book.find()
    .then(books => {		
		let returnedBooks = [];
		
		for (let i = 0; i < books.length; i++) {
			returnedBooks.push(books[i].toClient());
		}
		
        res.send(returnedBooks);
    }).catch(err => {
        res.status(500).send({
            message: err.message
        });
    });
};
 
// FIND a Book
exports.findOne = (req, res) => {
    Book.findById(req.params.bookId)
    .then(book => {
        if(!book) {
            return res.status(404).send({
                message: "Book not found with id " + req.params.bookId
            });            
        }
        res.send(book.toClient());
    }).catch(err => {
        if(err.kind === 'ObjectId') {
            return res.status(404).send({
                message: "Book not found with id " + req.params.bookId
            });                
        }
        return res.status(500).send({
            message: "Error retrieving Book with id " + req.params.bookId
        });
    });
};
 
// UPDATE a Book
exports.update = (req, res) => {
    // Find Book and update it
    Book.findOneAndUpdate({ _id: req.params.bookId }, {
		title: req.body.title,
		author: req.body.author,
		description: req.body.description,
		published: req.body.published
    }, {new: true})
    .then(book => {
        if(!book) {
            return res.status(404).send({
                message: "Book not found with id " + req.params.bookId
            });
        }
        res.send(book.toClient());
    }).catch(err => {
        if(err.kind === 'ObjectId') {
            return res.status(404).send({
                message: "Book not found with id " + req.params.bookId
            });                
        }
        return res.status(500).send({
            message: "Error updating Book with id " + req.params.bookId
        });
    });
};
 
// DELETE a Book
exports.delete = (req, res) => {
    Book.findByIdAndRemove(req.params.bookId)
    .then(book => {
        if(!book) {
            return res.status(404).send({
                message: "Book not found with id " + req.params.bookId
            });
        }
        res.send({message: "Book deleted successfully!"});
    }).catch(err => {
        if(err.kind === 'ObjectId' || err.name === 'NotFound') {
            return res.status(404).send({
                message: "Book not found with id " + req.params.bookId
            });                
        }
        return res.status(500).send({
            message: "Could not delete Book with id " + req.params.bookId
        });
    });
};

Server.js

server.js file:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json())
 
const cors = require('cors')
const corsOptions = {
  origin: 'http://localhost:8081',
  optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
 
// Configuring the database
const dbConfig = require('./app/config/mongodb.config.js');
const mongoose = require('mongoose');
 
mongoose.Promise = global.Promise;
 
// Connecting to the database
mongoose.connect(dbConfig.url)
.then(() => {
    console.log("Successfully connected to MongoDB.");    
}).catch(err => {
    console.log('Could not connect to MongoDB.');
    process.exit();
});
 
require('./app/route/book.route.js')(app);
 
// Create a Server
var server = app.listen(8080, function () {
 
  var host = server.address().address
  var port = server.address().port
 
  console.log("App listening at http://%s:%s", host, port)
 
})

2. React Redux Client

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---react-client-project-structure

2.1 Dependency

-> package.json:

{
  "name": "react-redux-nodejs",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Grokonez.com",
  "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'
});

2.3 Redux Action

actions/books.js :

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

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

2.5 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));
};

2.6 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>
        );
    }
}

2.7 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>ozenero</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;

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

– Run MongoDB: mongod.exe
– Run Nodejs project with commandlines: npm start
– Run the React App with command: yarn run dev-server

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

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---add-book

Show Books:

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---show-book

Check MongoDB database:

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---mongodb-book-adding-records

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

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---edit-book

Click Add Book button and check new Book list:

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---edit-book-return

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

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---result-remove-book

Check MongoDB Database:

react-redux-http-client-nodejs-restapi-express-mongoose-mongodb---mongodb-book-after-edit

Source Code

ReactReduxHttpClient
Nodejs-RestAPIs-MongoDB

0 0 votes
Article Rating
Subscribe
Notify of
guest
2.7K Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments