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 router
const router = require('./app/index.js');
// body parser
app.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 routes
const 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 Import
const toDo = require("./toDo/index.js");
const router = express.Router();
// Adding Routes
router.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 Public
router.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 Public
router.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 Public
router.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 Public
router.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 filter
function 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-do
function 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 react
import 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:
Top comments (0)