An Interest In:
Web News this Week
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
- March 26, 2024
Steps to Create A To-Do App With MERN stack
Tools You Will Need
Make sure Node and NPM are installed on your computer. You can download both at nodejs.org (NPM is included in your Node installation)
Tech Stack
Node.js
Express.js
MongoDB
React.js
Dependencies for Node.js
body-parser
mongoose
mongoose-auto-increment
Create your Node (Express) Backend
First create a folder for your project, called to-do-node (for example).
Then, open that folder in your code editor.
To create our Node project, run the following command in your terminal:
npm init -y
This will create a package.json file that will allow us to keep track of all our app scripts and manage any dependencies our Node app needs.
We'll use Express to create a simple web server for us that runs on port 3000.
So let's create an index file where our app starts to run with the name of index.js
.
const express = require('express')const app = express()const bodyParser = require('body-parser');//import routerconst router = require('./app/index.js');// body parserapp.use(bodyParser.urlencoded({ limit: '100mb', extended: true }))app.use(bodyParser.json({ limit: '100mb', extended: true }))app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization"); if ('OPTIONS' === req.method) { //respond with 200 res.send(200); } else { //move on next(); }});app.get('/', (req, res) => { res.send("incorrect route");})//add routesconst base = '/api/v1/';app.use(base, router);app.listen(process.env.PORT || 3000, () => console.log('Running on port 3000!'));
Then in our terminal, we will install dependencies which we need:
npm I express mongoose body-parser nodemon mongoose-auto-increment
Create a folder called app
inside the project folder and add index.js
file to defines routes.
const express = require('express');// Routes Importconst toDo = require("./toDo/index.js");const router = express.Router();// Adding Routesrouter.use('/to-do', toDo);module.exports = router;
Create folder name called config and add db.js
file to that.
db.js
file will have the following configurations:
const mongoose = require('mongoose');mongoose.connect('mongodb+srv://<mongodb_username>:<cluster_password>@cluster0.nxcni.mongodb.net/toDo?retryWrites=true&w=majority', { useNewUrlParser: true });module.exports = mongoose.connection;
Let's write APIs now.
Create a folder called toDo and model.js
, index.js
, and router.js
files in that.
index.js
module.exports = require("./router");
model.js
const mongoose = require('mongoose'); const autoIncrement = require('mongoose-auto-increment');const db = require('../config/db.js');autoIncrement.initialize(db);const schema = new config.mongoose.Schema({ toDos: [ { toDo: String, tag: String, tagColor: String, done: Boolean } ], createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now }, status: { type: Boolean, default: true }}, { strict: true});var toDo = mongoose.model('toDos', schema);module.exports = toDo;
router.js
const config = require('../config/routes.js');const router = config.express.Router();const collection = require('./model.js');// @route GET api/v1/to-do/list// @desc get users list with pagination// @access Publicrouter.get('/list', function (req, res) { if(!req.query.id) { res.status(200).send({data: []}); return false; } getToDosList(req.query.tag ? req.query.tag : '', req.query.id).then(resp => { res.status(200).send(resp[0]); }, err => { res.status(500).send({message: "Something went wrong, please try after sometime"}); })});// @route CREATE api/v1/to-do/add// @desc add to-do// @access Publicrouter.post('/add', function(req, res) { if(!req.query.id) { collection.create({toDos: [{toDo: req.body.text, done: false, tag: req.body.tag, tagColor: req.body.tagColor}]}, function (err, toDo) { if (!err) { return res.status(200).json({error: false, data: toDo, message: 'success'}) } else { return res.status(500).send({error: true, message: 'Error adding to-do'}) } }); } else { let updateData = { $push: { "toDos": {toDo: req.body.text, done: false, tag: req.body.tag, tagColor: req.body.tagColor} } }; updateToDo({_id: req.query.id}, updateData).then(toDo => { return res.status(200).json({error: false, data: toDo, message: 'success'}) }, err => { return res.status(500).send({error: true, message: 'Error adding to-do'}) }) }});// @route UPDATE api/v1/to-do/done// @desc update toDo status// @access Publicrouter.put('/done/:userId/:toDoId', function(req, res) { let updateData = { $set: { "toDos.$.done": req.body.done } }; updateToDo({_id: req.params.userId, "toDos._id": req.params.toDoId}, updateData).then((toDo) => { return res.status(200).json({error: false, message: 'Updated successfully'}) }, err => { return res.status(500).send({error: true, message: err}) })});// @route UPDATE api/v1/to-do/delete// @desc delete toDo// @access Publicrouter.put('/delete/:userId/:toDoId', function(req, res) { let updateData = { "$pull": { "toDos": { "_id": req.params.toDoId } } } updateToDo({_id: req.params.userId, "toDos._id": req.params.toDoId}, updateData).then((toDo) => { return res.status(200).json({error: false, message: 'Updated successfully'}) }, err => { return res.status(500).send({error: true, message: err}) })});// function to get to-dos list with tag filterfunction getToDosList(tag, id) { return new Promise(function(resolve, reject) { let agg = [ { "$unwind": "$toDos" }, { "$match": { $or: [{"_id": id}, {"toDos.tag": {$regex: `${tag}.*`, $options: "i" }}] } }, { "$group": { _id: null, data: {$push: "$toDos"} } } ] collection.aggregate(agg, function(err, response) { if(err) return reject({message: "Something went wrong"}) if(!response) return reject({message: "Error while getting remitters data"}) return resolve(response) }) })}//function to update to-dofunction updateToDo(query, updateData) { return new Promise(function(resolve, reject) { collection.findOneAndUpdate(query, updateData, {new: true}, function (err, resp) { if (err) return reject({error: 1, message: "There was a problem while updating data"}); return resolve(resp); } ); })}function getToDos(query) { return new Promise(function(resolve, reject) { collection.find(query, function (err, resp) { if (err) return reject({error: 1, message: "There was a problem while updating data"}); return resolve(resp); } ); })}module.exports = router
Finally, we can run our app by running nodemon index.js
in our terminal and we should see that app is running on port 3000.
Dependencies for React.js
bootstrap
react-bootstrap
react-icons
Create your React Frontend
After creating our backend, let's move to the frontend.
Open another terminal tab and use create-react-app to create a new React project with the name to-do-react (for example):
npx create-react-app to-do-react
After that, we will have a React app with all of its dependencies installed.
Now, go to the folder
cd to-do-react
Change directory to src and run the below commands:
cd src
rm *
Now create index.js file by running below command:
touch index.js
This file will render our app to an HTML file which is in the public folder. Also, create a folder name components
with the file name app.js
.
mkdir components && cd components && touch app.js
app.js will contain our To-Do app.
Edit index.js
file in src:
import React from 'react';import ReactDOM from 'react-dom';import App from './components/app';import 'bootstrap/dist/css/bootstrap.min.css';ReactDOM.render(<App/>, document.getElementById('root'));
Create a folder with the name of api
and add the file with name of to-do.js
and write API calls in that file as below:
import axios from 'axios';let base = 'http://localhost:3000/api/v1/';export default function api(url, method='GET', data={}) { return new Promise(function(resolve, reject) { const requestOptions = { url: base + url, method: method, headers: { 'Content-Type': 'application/json' }, data }; axios(requestOptions) .then(function (response) { resolve(response.data); }) .catch(function (error) { reject(error); }); });}export function AddToDoAPI(data) { return new Promise(function(resolve, reject) { api(`to-do/add?id=${localStorage.userId ? localStorage.userId : ''}`, 'POST', data) .then((resp) => { return resolve(resp); }, (error) => { return reject(error.response.data.message); }) })}export function GetToDoListAPI(tag='') { return new Promise(function(resolve, reject) { api(`to-do/list?id=${localStorage.userId ? localStorage.userId : ''}&tag=${tag}`) .then((resp) => { return resolve(resp); }, (error) => { console.log(error) debugger return reject(error.response.data.message); }) })}export function UpdateToDoAPI(data, toDoId) { return new Promise(function(resolve, reject) { api(`to-do/done/${localStorage.userId}/${toDoId}`, 'PUT', data) .then((resp) => { return resolve(resp); }, (error) => { return reject(error.response.data.message); }) })}export function DeleteToDoAPI(toDoId) { return new Promise(function(resolve, reject) { api(`to-do/delete/${localStorage.userId}/${toDoId}`, 'PUT', {}) .then((resp) => { return resolve(resp); }, (error) => { return reject(error.response.data.message); }) })}
Edit app.js
in components:
import React, {Component} from 'react';// Bootstrap for reactimport Container from 'react-bootstrap/Container';import Row from 'react-bootstrap/Row';import Col from 'react-bootstrap/Col';import Button from 'react-bootstrap/Button';import InputGroup from 'react-bootstrap/InputGroup';import FormControl from 'react-bootstrap/FormControl';import ListGroup from 'react-bootstrap/ListGroup';import Form from 'react-bootstrap/Form'import Dropdown from 'react-bootstrap/Dropdown'import DropdownButton from 'react-bootstrap/DropdownButton'import {AddToDoAPI, GetToDoListAPI, UpdateToDoAPI, DeleteToDoAPI} from '../api/to-do'import { BsStop, BsX } from 'react-icons/bs';import {Badge} from "react-bootstrap";class AppComponent extends Component { constructor(props) { super(props); // Setting up state this.state = { userInput : "", list:[], selectedTag: "Other", selectedTagColor: "grey", tags: [ {tagName: 'Other', color: 'grey'}, {tagName: 'Work', color: 'red'}, {tagName: 'Personal', color: 'green'} ] } } componentDidMount() { this.getItems() } // Set a user input value updateInput(value){ this.setState({ userInput: value, }); } // Set a selected tag value updateTag(value){ this.setState({ selectedTag: value.split(" ")[0], selectedTagColor: value.split(" ")[1] }); } // Add item if user input in not empty addItem(event){ if(event.code === 'Enter') { AddToDoAPI({text: this.state.userInput, tag: this.state.selectedTag, tagColor: this.state.selectedTagColor}).then(resp => { if(!localStorage.userId) { localStorage.setItem('userId', resp.data._id); } this.getItems() }) } } //Get to-do list getItems(tag='') { GetToDoListAPI(tag).then(resp => { // Update list const list = [...resp ? resp.data : []]; // reset state this.setState({ list, userInput: "" }); }) } UpdateToDo(val, id) { UpdateToDoAPI({done: val}, id).then(resp => { this.getItems() }) } // Function to delete item from list use id to delete deleteItem(id) { DeleteToDoAPI(id).then(resp => { this.getItems() }) } render(){ return( <Container> <Row style={{ display: "flex", justifyContent: "center", alignItems: "center", fontSize: '3rem', fontWeight: 'bolder', fontFamily: 'DejaVu Sans Mono, monospace', paddingTop: 2 }} >TODO LIST </Row> <hr style={{marginTop: 0}}/> <Row> <Col md={{ span: 5, offset: 4 }}> <InputGroup className="mb-3"> <DropdownButton variant="outline-secondary" id="input-group-dropdown-2" title={this.state.selectedTag} align="end" size="lg" style={{backgroundColor: 'white'}} onSelect = {e => this.updateTag(e)} > {this.state.tags.map(tag => ( <span style={{display: 'flex'}}><BsStop style={{fontSize: 30, marginTop: 1, color: tag.color}}/><Dropdown.Item key={tag.tagName} eventKey={tag.tagName + ' ' + tag.color}>{tag.tagName}</Dropdown.Item></span> ))} </DropdownButton> <FormControl placeholder="add item . . . " size="lg" value = {this.state.userInput} onChange = {item => this.updateInput(item.target.value)} onKeyPress = {e => this.addItem(e)} aria-label="add something" aria-describedby="basic-addon2" /> </InputGroup> </Col> </Row> {this.state.list.length ? <Row> <Col md={{ span: 5, offset: 4 }} style={{paddingBottom: 18}}> <Button variant="primary" style={{paddingTop: 0, paddingBottom: 0}} onClick={e => this.getItems('')} size="sm">All</Button>{' '} <Button variant="secondary" style={{paddingTop: 0, paddingBottom: 0}} onClick={e => this.getItems('Other')} size="sm">Other</Button>{' '} <Button variant="danger" style={{paddingTop: 0, paddingBottom: 0}} onClick={e => this.getItems('Work')} size="sm">Work</Button>{' '} <Button variant="success" style={{paddingTop: 0, paddingBottom: 0}} onClick={e => this.getItems('Personal')} size="sm">Personal</Button>{' '} </Col> </Row> : null } <Row> <Col md={{ span: 5, offset: 4 }}> <ListGroup> {/* map over and print items */} {this.state.list.map(item => {return( <ListGroup.Item variant="white" action key={item._id}> <Form.Group id="formGridCheckbox" style={{display: 'flex'}}> <Form.Check type="checkbox" style={{width: 10}} className="my-1 mr-sm-2" onChange={e => this.UpdateToDo(!item.done, item._id)} checked={item.done}/> {item.done ? <span style={{textDecoration: 'line-through', marginTop: 5, width: 380}}>{item.toDo}</span> : <span style={{marginTop: 5, width: 380}}>{item.toDo}</span>} <BsStop style={{fontSize: 25, marginTop: 1, color: item.tagColor, float: 'right', width: 30}}/> <BsX onClick = { () => this.deleteItem(item._id) } style={{float: 'right', fontSize: 25, marginLeft: 'auto', width: 30}} /> </Form.Group> </ListGroup.Item> )})} </ListGroup> </Col> </Row> </Container> ); }}export default AppComponent;
Start the server by typing the following command in the terminal:
npm start
Output: Open http://localhost:3000
in browser:
Original Link: https://dev.to/chytrakr/steps-to-create-a-to-do-app-with-mern-stack-4912
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To