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
1. Nodejs/Express Server
2. 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:
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
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:
Show Books:
Check MongoDB database:
Click on a Book’s title, app goes to Edit Page:
Click Add Book button and check new Book list:
Click on certain Remove button to remove certain Book.
For example, removing Origin:
Check MongoDB Database:
Source Code
– ReactReduxHttpClient
– Nodejs-RestAPIs-MongoDB