In: Computer Science
create a todo app using React and MongoDB. User can also delete the tasks, add tasks, update tasks and edit tasks using edit, del, update and add button.
First of all, this is an Original Web App build By Me. If you want, you can see it on github as I can't paste my link so had to copy my project to help you. SO PLEASE DROP A LIKE ON THIS POST.
A Todo App with React, MongoDB, ExpressJS, and NodeJS -
Project Setup -
1. Install Node
If you're a Linux User:
Open a terminal
sudo apt install nodejs
check if node installed successfully
node -v
install NPM, a package manager for Node
sudo apt install npm
npm -v
Installing MongoDB
MongoDB is the database that we will use to store our todo’s -
mkdir todo-app
cd todo-app
create the backend folder
mkdir todo-backend
create the front end react app
npx create-react-app todo-frontend
now you should have a folder structure like so
todo-app
- todo-backend
- todo-frontend
- node_modules
- public
- src
- package.json
- .gitignore
- README.md
Building backend
navigate to the todo-backend
folder -
cd todo-backend
run
npm init -y
install packages that we will need to build the express server
npm install express body-parser mongoose --save
create the index.js
which will store our server
initialization logic.
touch index.js
If you have VS Code installed, you can open the project in VS Code using -
code .
otherwise, just open it in your favorite code editor
copy this code into the index.js file
const express = require("express") // our express server
const app = express() // generate an app object
const bodyParser = require("body-parser") // requiring the body-parser
const PORT = process.env.PORT || 3000 // port that the server is running on => localhost:3000
app.use(bodyParser.json()) // telling the app that we are going to use json to handle incoming payload
app.listen(PORT, () => {
// listening on port 3000
console.log(`listening on port ${PORT}`) // print this when the server starts
})
Run the server -
node index.js
You should see the message print out to the terminal.
Nodemon(highly recommend but not required)
This handy tools allows you to edit your server files and see
the change propagate in real time without starting the server each
time with node index.js
install by running -
npm install -g nodemon
Run the server again using
nodemon index.js
If you choose to not use this tool, make sure to restart the
server each time you make a change to the todo-backend
files
Connecting to MongoDB
create the models folder in the root of the
todo-backend
folder
mkdir models
Connecting to the database is really simple. Create an
index.js
file in the models
directory
touch models/index.js
copy the follow code into the index.js
file
const mongoose = require("mongoose")
mongoose.connect("mongodb://localhost/todo-app", {
// connecting to the mongodb database name: "todo-app" locally
keepAlive: true, // keeping the connection alive
useNewUrlParser: true,
useUnifiedTopology: true,
})
mongoose.set("debug", true) // enabling debugging information to be printed to the console for debugging purposes
mongoose.Promise = Promise // setting mongoose's Promise to use Node's Promise
Create the Todo Model
A model is just a blue print for an object that we want to store in our database. It describes the structure and fields that pertains to a specific kinds of data.
Create the todo.js
file that will store our
model
touch models/todo.js
copy the code below into the todo.js
file
const mongoose = require("mongoose") // requiring the mongoose package
const todoSchema = new mongoose.Schema({
// creating a schema for todo
task: {
// field1: task
type: String, // task is a string
unique: true, // it has to be unique
required: true, // it is required
},
completed: {
// field2: completed
type: Boolean, // it is a boolean
default: false, // the default is false
},
})
const todoModel = mongoose.model("Todo", todoSchema) // creating the model from the schema
module.exports = todoModel // exporting the model
Now that we have our model, let’s use it. In the
index.js
file of the models
dictory , add
the following line of code to the bottom.
module.exports.Todo = require("./todo") // requiring the todo model that we just created in mongodb
Next, add the following statement to the top of the index.js
file where we stored server initialization logic. Note: we did not
do require("./models/index.js")
because in Node Js
whenever we require a directory, it will search for the file named
index
first.
const db = require("./models/")
Run the server to make sure that nothing is broken. Chances are,
you ran into this error: MongoNetworkError: failed to connect
to server [localhost:27017]
in the terminal which is because
we have not started the mongodb server locally. Run the following
command to start the mongodb service on mac.
brew services start mongodb-community
Restart the node server and we should see that everything is fine
listening on port 3000
Mongoose: todos.ensureIndex({ task: 1 }, { unique: true, background: true })
(node:10201) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.
Establishing API Endpoints
API endpoints are just URL’s that can be used to make request to a service or a system.
How should we structure our API Endpoints?
icecream.com/flavors
making a
GET
request to this API endpoint will retrieve all the
flavors.Our todo API will have the following endpoints
GET /todos //getting all todos
POST /todos //adding a new todo
PUT /todos/:id //updating a todo with the matching id
DELETE /todos/:id //deleting a todo with matching id
Lets Create Some Routes
Routes in Node and Express are just API endpoints that we are exposing.
Update the index.js
file at the root containing the
server initialization logic to look like the section below. This
might be quite a bit of code, and you will probably feel
overwhelmed. Do not be discouraged. I will clarify each part. Those
of you who are familiar with ExpressJS will find this section
familiar, so feel free to skim over the section below.
const express = require("express")
const app = express()
const bodyParser = require("body-parser")
const PORT = process.env.PORT || 3000
const db = require("./models/")
app.use(bodyParser.json())
function success(res, payload) {
return res.status(200).json(payload)
}
app.get("/todos", async (req, res, next) => {
try {
const todos = await db.Todo.find({})
return success(res, todos)
} catch (err) {
next({ status: 400, message: "failed to get todos" })
}
})
app.post("/todos", async (req, res, next) => {
try {
const todo = await db.Todo.create(req.body)
return success(res, todo)
} catch (err) {
next({ status: 400, message: "failed to create todo" })
}
})
app.put("/todos/:id", async (req, res, next) => {
try {
const todo = await db.Todo.findByIdAndUpdate(req.params.id, req.body, {
new: true,
})
return success(res, todo)
} catch (err) {
next({ status: 400, message: "failed to update todo" })
}
})
app.delete("/todos/:id", async (req, res, next) => {
try {
await db.Todo.findByIdAndRemove(req.params.id)
return success(res, "todo deleted!")
} catch (err) {
next({ status: 400, message: "failed to delete todo" })
}
})
app.use((err, req, res, next) => {
return res.status(err.status || 400).json({
status: err.status || 400,
message: err.message || "there was an error processing request",
})
})
app.listen(PORT, () => {
console.log(`listening on port ${PORT}`)
})
The success
function accepts the res
object and the payload
and send a 200
status code with the payload
in the response
function success(res, payload) {
return res.status(200).json(payload)
}
GET /todos/ - getting all the todos.
app.get()
tells express that the route
"/todos"
is a get route. The second paramater
async (req,res,next)=>{ //some code }
is the
function that gets called when the server receives this
request.
Inside the function, we are getting the all todos using
await db.Todo.find({})
. If it was successful we return
success(res, todos)
with all the todos. Otherwise, we
received an error and send a 400
status code with a
message.
app.get("/todos", async (req, res, next) => {
try {
const todos = await db.Todo.find({})
return success(res, todos)
} catch (err) {
next({ status: 400, message: "failed to get todos" })
}
})
POST /todos/ - creating a new todo we called
await db.Todo.create(req.body)
to create a new todo.
Here req.body
contains the request payload that will
contain the task to be created.
app.post("/todos", async (req, res, next) => {
try {
const todo = await db.Todo.create(req.body)
return success(res, todo)
} catch (err) {
next({ status: 400, message: "failed to create todos" })
}
})
PUT /todos/:id - updating a todo with the given
id we update the todo by calling await
db.Todo.findByIdAndUpdate(req.params.id, req.body,
{new:true})
. here req.params
contains all the
route parameters that we specified; Here we have one:
"/todos/:id"
called id
.
req.body
contains the new information that we want to
update the task with. The last parameter {new:true}
tells mongoose that we want the updated task to be returned.
app.put("/todos/:id", async (req, res, next) => {
try {
const todo = await db.Todo.findByIdAndUpdate(req.params.id, req.body, {
new: true,
})
return success(res, todo)
} catch (err) {
next({ status: 400, message: "failed to update todo" })
}
})
DELETE /todos/:id - deleting a todo with the
given id we call await
db.Todo.findByIdAndRemove(req.params.id)
app.delete("/todos/:id", async (req, res, next) => {
try {
await db.Todo.findByIdAndRemove(req.params.id)
return success(res, "todo deleted!")
} catch (err) {
next({ status: 400, message: "failed to delete todo" })
}
})
This is our error handler. Whenever we call next({status:
400, message: "failed to do something"})
, we are passing the
error object to the error handler.
app.use((err, req, res, next) => {
return res.status(err.status || 400).json({
status: err.status || 400,
message: err.message || "there was an error processing request",
})
})