How to Build REACT MERN Stack Todo CRUD Application

React MERN Stack example; This is a step-by-step tutorial that helps you comprehend how to build a progressive MERN stack Todo CRUD application in React Js using the latest Bootstrap 5 CSS framework.

Moreover, we will show you how to create REST APIs using Node and Express and store the data into MongoDB dynamically.

CRUD means create, read, update and delete. Almost all of us start our development career with CRUD operations development, and this is also the basic building block of software development.

Every application works on the same pattern; think about what you do with web and mobile apps? You create data, read data, update data and delete data.

In this React CRUD operations tutorial, we will teach you how to create a basic todo app, which will allow you to create tasks, read tasks, update tasks, and delete tasks. Additionally, you will also learn how to create simple REST APIs using node js and express js.

Eloquent REACT MERN Stack CRUD Example

  • Step 1: Install New React Project
  • Step 2: Add Bootstrap 5 in React
  • Step 3: Install Axios Package in React
  • Step 4: Create Todo Crud Components
  • Step 5: Create REST APIs with Node and Express
  • Step 6: Implement Create Functionality
  • Step 7: Todo Items Read + Delete
  • Step 8: Integrate Update Feature
  • Step 9: Start React Application

Install New React Project

Let’s hit the road by installing the latest version of React application, get to the terminal, type the below command and install this noted prodigy.

npx create-react-app react-mern-stack

Enter into the project.

cd react-mern-stack

Add Bootstrap 5 in React

To design your Todo CRUD app, we will use the latest Bootstrap 5 CSS framework, and It helps eloquently create a responsive layout.

npm install bootstrap

In order to take advantage of Bootstrap, add the bootstrap CSS in the main src/App.js file.

import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

Install Axios Package in React

If you are a novice developer and recently trying your hands on MERN stack development, then Axios is the package you will ever need. It’s a handy package that lets you deal with HTTP requests in React environment.

npm install axios

Create Todo Crud Components

In this section, you have to create two components; in these templates, we will add, view, update, and delete to-do items.

First, create src/components folder.

Then, create the src/components/addTodo.components.js file and add the suggested code within the file.

import React, { Component } from 'react';
import axios from 'axios';

export default class AddTodoComponent extends Component {
    render() {
        return (
            <div>
                
            </div>
        )
    }
}

Again, create the src/components/viewTodo.components.js file and place the following code within the file.

import React, { Component } from 'react';
import axios from 'axios';

export default class ViewTodoComponent extends Component {
    render() {
        return (
            <div>
                
            </div>
        )
    }
}

Let’s club these components together and register them in the src/App.js file.

import React from 'react';

import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import './App.css';

import AddTodoComponent from './components/addTodo.components';
import ViewTodoComponent from './components/viewTodo.components';

function App() {
  return (
    <div className="App container mt-5">
       <AddTodoComponent />
       <ViewTodoComponent />
    </div>
  );
}

export default App;

Create REST APIs with Node and Express

Setting up node backend is not as peculiar as it seems; fret not, you don’t have to move heaven and earth simultaneously. So, without being so solicitous, let’s jump on to react project’s root.

Create a new folder ‘server’; all the significant backend related code goes into it.

Immediately after executing the npm init command to formulate a new package.json, the important locus of your node server files.

npm init

You have to install the essentials packages that help to create CRUD REST APIs.

npm install mongoose express cors body-parser

Also, add the nodemon package; this package relentlessly eradicates the server restarting issue.

npm install nodemon --save-dev

Next, create a new server/db.js file to keep the MongoDB database name.

module.exports = {
    database: 'mongodb://localhost:27017/tododb'
};

Next, create a server/Todo.js file; in this file, we define the logical structure of the Todo crud app data, along with MongoDB database collection.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

let todoSchema = new Schema({
  task: {
    type: String
  }
}, {
    collection: 'todos'
  })

module.exports = mongoose.model('Todo', todoSchema)

Again, create a server/todo.route.js file; in this file, we declare all the create, read, update and delete methods that help to form the REST APIs.

let mongoose = require('mongoose');
let express = require('express');
let router = express.Router();

let todoSchema = require('./Todo');


router.route('/').get((req, res, next) => {
  todoSchema.find((error, data) => {
      if (error) {
        return next(error)
      } else {
        res.json(data)
      }
    })
})


router.route('/create-todo').post((req, res, next) => {
  todoSchema.create(req.body, (error, data) => {
    if (error) {
      return next(error)
    } else {
      console.log(data)
      res.json(data)
    }
  })
});


router.route('/edit-todo/:id').get((req, res, next) => {
  todoSchema.findById(req.params.id, (error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
})


router.route('/update-todo/:id').put((req, res, next) => {
  todoSchema.findByIdAndUpdate(req.params.id, {
    $set: req.body
  }, (error, data) => {
    if (error) {
      return next(error);
    } else {
      res.json(data)
      console.log('Todo updated')
    }
  })
})


router.route('/delete-todo/:id').delete((req, res, next) => {
  todoSchema.findByIdAndRemove(req.params.id, (error, data) => {
    if (error) {
      return next(error);
    } else {
      res.status(200).json({
        msg: data
      })
    }
  })
})

module.exports = router;

In the end, create a server/index.js file. Ideally, this file can be named anything, but it invokes the node server eloquently. It manages node app startup, routing and serves the primary purpose to add function functionality.

let express = require('express');
let mongoose = require('mongoose');
let cors = require('cors');
let bodyParser = require('body-parser');
let mongoDb = require('./db');

const TodoRoute = require('./todo.route')

mongoose.Promise = global.Promise;
mongoose.connect(mongoDb.database, {
    useUnifiedTopology: true,
    useNewUrlParser: true
}).then(() => {
  console.log('Database connected!')
},
error => {
    console.log(error)
  }
)

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.use(cors());
app.use('/api', TodoRoute)


const port = process.env.PORT || 5050;
const server = app.listen(port, () => {
  console.log('Connected on : ' + port)
})

app.use(function (err, req, res, next) {
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Implement Create Functionality

We used the Bootstrap, Express REST API, and Axios; these packages are profoundly helping us to create a clean todo UI, alternatively making the HTTP post request to add the todo item.

In this section we have to update the src/components/addTodo.components.js file.

import React, { Component } from 'react';
import axios from 'axios';

export default class AddTodoComponent extends Component {
    
    constructor(props) {
        super(props)
    
        this.onTaskChange = this.onTaskChange.bind(this);
        this.onSubmit = this.onSubmit.bind(this);
    
        this.state = {
           task: ''
        }
      }

      refreshPage() {
        window.location.reload(false);
      }      
    
      onTaskChange(e) {
        this.setState({ 
            task: e.target.value 
        })
      }
    
      onSubmit(e) {
        e.preventDefault()
        const todoObject = {
            task: this.state.task
        };
        axios.post('http://localhost:5050/api/create-todo', todoObject)
          .then((res) => {
            console.log(res.data)
          });
    
        this.setState({ task: '' })
        this.refreshPage()
      }
    
      render() {
          return (
            <div>
                <form onSubmit={this.onSubmit}>
                    <div className="form-group">
                        <label className="mb-2"><strong>Create Task</strong></label>
                        <input type="text" className="form-control" value={this.state.task} onChange={this.onTaskChange} />
                    </div>

                    <div className="d-grid mt-3">
                        <input type="submit" value="+ Create" className="btn btn-primary" />
                    </div>
                </form>
            </div>
          )
       }

}

Todo Items Read + Delete

In this section, we show how to display the todo list and delete the todo item pragmatically; we use two apis to accomplish the tasks. The getTodos() method fetches the list from the mongo database, whereas the deleteTodo() method deletes the todo item from the database.

Open and update the src/components/viewTodo.components.js file.

import React, { Component, useState } from 'react';
import axios from 'axios';

export default class ViewTodoComponent extends Component {

    constructor(props) {
        super(props)

        this.state = {
            todos: []
        };

        this.deleteTodo = this.deleteTodo.bind(this);
    }

    componentDidMount() {
      this.getTodos();
    }

    getTodos() {
        const headers = { 'Content-Type': 'application/json' }

        const endpoint = 'http://localhost:5050/api';

        axios.get(endpoint, { headers })
        .then(response => {
            this.setState({
                todos: response.data
            });
        })
        .catch((error) => {
            console.log(error);
        })        
    }

    deleteTodo(id) {
        axios.delete('http://localhost:5050/api/delete-todo/' + id)
            .then((res) => {
                alert('Todo deleted!')
                this.getTodos();
            }).catch((error) => {
                console.log(error)
           })
    }

    render() {
        const { todos } = this.state;
        return (
            <>
                <ul className="list-group mt-3">
                    {todos.map((data) => (
                        <li key={data._id} className="list-group-item d-flex justify-content-between align-items-start">
                            <div className="ms-2 me-auto">
                            <div className="fw-bold">{data.task}</div>
                            </div>

                            <span className="badge bg-success rounded-pill" onClick={this.openModal}>Update</span> 
                            &nbsp;
                            <span className="badge bg-danger rounded-pill" onClick={this.deleteTodo.bind(this, data._id)}>Delete</span>
                        </li>
                    ))}
                </ul>          
            </>    
        )
    }

}

Update Todo + Integrate Modal Dialog in React

Now, we have to install the react-modal package.

npm i react-modal

Next, open and update the src/components/viewTodo.components.js file.

import React, { Component } from 'react';
import axios from 'axios';
import Modal from 'react-modal';


export default class ViewTodoComponent extends Component {
    
    taskObj = {
        _id: '',
        task: ''
    }

    state = {
        modalIsOpen: false,
        secondModalIsOpen: false
      };
    
      openModal = (data) => {
        this.setState({ 
            modalIsOpen: true 
        });

        this.setTodoVal(data);
      };

      setTodoVal = (data) => {
         this.taskObj = {
            _id: data._id,
            task: data.task
        }
      }
    
      closeModal = () => {
        this.setState({ modalIsOpen: false });
      };
    
      openSecondModal = () => {
        this.setState({ secondModalIsOpen: true });
      };
    
      closeSecondModal = () => {
        this.setState({ secondModalIsOpen: false });
      };
      
      

    constructor(props) {
        super(props)

        this.state = {
            todos: []
        };

        this.deleteTodo = this.deleteTodo.bind(this);
    }

    componentDidMount() {
      this.getTodos();
    }

    getTodos() {
        const headers = { 'Content-Type': 'application/json' }

        const endpoint = 'http://localhost:5050/api';

        axios.get(endpoint, { headers })
        .then(response => {
            this.setState({
                todos: response.data
            });
        })
        .catch((error) => {
            console.log(error);
        })        
    }

    deleteTodo(id) {
        axios.delete('http://localhost:5050/api/delete-todo/' + id)
            .then((res) => {
                alert('Todo deleted!')
                this.getTodos();
            }).catch((error) => {
                console.log(error)
           })
    }

    
    onTaskChange(e) {
        this.taskObj = {
            _id: this.taskObj._id,
            task: e.target.value
        }        

        var e = new Event('input', { bubbles: true });
        this.myinput.dispatchEvent(e);
    } 
    

    
    refreshPage() {
        window.location.reload(false);
    }

    onUpdate = () => {
        axios.put('http://localhost:5050/api/update-todo/' + this.taskObj._id, this.taskObj)
        .then((res) => {
          console.log('Todo updated' + res)
          this.refreshPage()
        }).catch((error) => {
          console.log(error)
        })
    }

   


    render() {
        const { todos } = this.state;
        return (
            <>
                <ul className="list-group mt-3">
                    {todos.map((data) => (
                        <li key={data._id} className="list-group-item d-flex justify-content-between align-items-start">
                            <div className="ms-2 me-auto">
                               <div className="fw-bold">{data.task}</div>
                            </div>

                            <span className="badge bg-success rounded-pill" onClick={this.openModal.bind(this, data)}>Update</span> 
                            &nbsp;
                            <span className="badge bg-danger rounded-pill" onClick={this.deleteTodo.bind(this, data._id)}>Delete</span>
                        </li>
                    ))}
                </ul>

                {/* Edit */}
                <Modal isOpen={this.state.modalIsOpen} onRequestClose={this.closeModal} ariaHideApp={false}>
                    
                    <div className="container">
                            <div className="form-group">
                                <label className="mb-2"><strong>Update Task</strong></label>
                                <input 
                                    type="text" 
                                    className="form-control" 
                                    defaultValue={this.taskObj.task} 
                                    onChange={(e) => {this.onTaskChange(e)}} ref={(input)=> this.myinput = input}
                                />
                            </div>

                            <div className="d-grid mt-3 gap-2">
                                <input type="button" onClick={this.onUpdate} value="Update" className="btn btn-success"/>
                                <button onClick={this.closeModal} className="btn btn-warning">close</button>
                            </div>
                    </div>
                </Modal>                
            </>    
        )
    }

}

We ideally did two things to update the todo list data; first, we installed the react modal package. This will open the modal dialog carrying the todo clicked todo value. Secondly, we write the logic to handle the HTTP put call that uses node express API to update the data.

Start React Application

This is the very last but significant step that has to be done with fidelity; primary idea is to start the node server, react app and view the app in the browser for testing.

Open a terminal and use the following command to run MongoDB; we assume you have already configured MongoDB on your development system.

mongo

Again, open another terminal and start the node server.

nodemon

Eventually, execute the command to start the react app.

npm start
http://localhost:3000

REACT MERN Stack Todo CRUD

Conclusion

This pragmatic guide explained how to ideally create the basic React Todo CRUD application using the MERN stack. This eloquent guide addressed the essential MERN stack topics bit by bit and focused on every topic precisely and subtly.

You learned how to create a basic react app, add essential npm packages to style, and make the HTTP requests concerning the crud app; along with that, we set up the node back-end server in react app using required npm packages.

To store the data, we used MongoDB and learned how to store the crud data using the React platform, and this tutorial might be a gold mine for those who are new to MERN stack development.