#100DaysOfCode Day 36: MERN Stack RESTful To-Do List Application

Richard Russell
Cold Brew Code
Published in
8 min readJul 6, 2020

Hello, World!

A camera shot of a laptop alongside a monitor showing lines of codes for a ToDo List program.
A camera shot of my work setup while working on this project.

Today, I will be building another To-Do list application using the MERN (MongoDB, Express.js, React.js, and Node.js) tech stack! In short, I will be building a simple REST API back-end using Node.js, Express, and MongoDB, while the front-end user-interface will be built with React.js

I find that iterating through different versions of the same project is really helpful in several ways. First, I could better understand the logic behind the application. For starters, I saw a pattern involving the use of the map() function to list down the To-Do list tasks. Moreover, I have a better understanding of how one could use HTTP configuration to send GET, POST, and DELETE request as an API to a database, and that a database can exist within an application itself. Second, doing these projects is similar to working on modules of math problems; practice through repetition allows a better understanding of the programming language. I now feel comfortable working back-end with Node.js and MongoDB.

Part I: Creating the Back-end

First things first, let us make our back-end! If you want a more detailed explanation, you can view my previous article here. However, I will still do my best to explain the code and its inner workings in this chapter!

Before diving into the fancy code, you should create your project’s directory! To do this, run the following command:

mkdir Mern-Todo

For the sake of this project, I will be calling the root directory Mern-Todo. The command mkdir stands for “make directory”, which is pretty self-explanatory what it does.

After creating your root directory, change your directory over to Mern-Todo using cd Mern-Todo and initialize your Node Package Manager:

npm init

The command above will initialize the directory according to the Node development environment. In short, it will create a package.json file, which contains the details of the project. As of now, you won’t have to edit the package.json file in any way; just write down the author and name of the program according to your liking.

Afterward, we need to install the necessary dependencies to make our back-end application. By default, the dependencies that we are going to install for our back-end work is the following: body-parser, cors, mongoose, and express. You can install these dependencies by running the following command:

npm install express body-parser cors mongoose

You can confirm that these dependencies have been installed by opening your package.json and view the "dependencies" part of the JSON file.

Next, we will code the foundation of our back-end application! Create a file, index.js, and write down the following code:

/*Import project dependencies*/
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
/*Initialize Express server*/
const app = express();
/*Call body-parser and cors middleware*/
var corsOptions = {
origin: 'http://localhost:4000'
}
app.use(cors(bodyOptions));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true})));'
/*Create default route*/
app.get('/', (req, res) => {
res.json({message: "Welcome to the REST API"});
});
/*Set PORT and listen for request*/
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server running on port: ${PORT}`);
});

Next, we will configure our MongoDB database! To do this, create a new directory within your root folder. We will call this directory as db. Inside db, there should be two important files: server.js and model.js. The server JavaScript file will be used to initialize the server before starting it in our main index.js file, while our model file will be used to create the schema of our database.

Your server.js file should be as the following:

const mongoose = require('mongoose');
mongoose.Promsie = global.Promise;
const db = {};
db.mongoose = mongoose;
db.url = 'mongodb://localhost:27017/todo';
db.models = require('./model.js');
module.exports = db;

Starting with the first line, we are making sure that our Mongoose dependency will be used in this part of the code. Next, we set the value of mongoose.Promise = global.Promise. This is done because the promise functionality of Mongoose is deprecated. We then create a new constant with the name db. This constant will be important in starting our database in our main index.js file. db.mongoose tells the computer that our database will be a MongoDB database with the help of Mongoose, db.url shows the URL of our database (and the port, which is an important detail to not miss). Lastly, db.models will be the schema of the data, which we will define soon. Our db constant is then exported as a module.

Now, your model code should look exactly like the following:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//Create schema for Todo List
const TodoSchema = new Schema({
action: {
type: String,
required: [true, 'The todo text field is required']
}
})
//Create model for todo
const Todo = mongoose.model('todo', TodoSchema);
module.exports = Todo;

Afterward, update your index.js file to include the Mongoose database configuration using the following code:

const db = require('./db/server.js');
db.mongoose
.connect(db.url, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("Connected to the database!");
})
.catch(err => {
console.log("Cannot connect to the database!", err);
process.exit();
});

Once you have finished activating the MongoDB sever with the code above, it is time to define our routes! Create a new directory within your root folder and call it routes. Inside, you should create a new file named api.js and write the following code:

const express = require('express');
const router = express.Router();
const Todo = require('../db/model');
router.get('/todos', (req, res, next) => {
Todo.find({}, 'action')
.then(data => res.json(data))
.catch(next)
});
router.post('/todos', (req, res, next) => {
if(req.body.action){
Todo.create(req.body)
.then(data => res.json(data))
.catch(next)
}else{
res.json({
error: "The input field is empty!"
})
}
});
router.delete('/todos/:id;, (req, res, next) => {
Todo.findOneAndDelete({'_id': req.params.id})
.then(data => res.json(data))
.catch(next)
});
module.exports = router;

After making the router and its respective functions, you should call the router to your index.js file with the following code:

app.use('/api', './routes/api');

All-in-all, your index.js code should look like the following:

And, we are done with our back-end! Don’t forget to test all API endpoints using Postman.

Part II: Making the Front-end

Now, it is time to create our user-interface. To do this, we will use create-react-app alongside npx. In your root directory — the same directory as your backend code — run the following command:

npx create-react-app client

Creating a React app will take a while, so you can take a short cat nap! Once the React app have been created, you will have to edit your project’s main package.json. Inside the "scripts" section, add the following code:

"start": "node index.js",
"dev": "npx concurrently \"npx nodemon index.js\" \"cd client && npm start\""

This way, whenever you want to run your application or any other MERN-stack apps, you can simply type npm run dev, and the script will handle your development workflow. This is really efficient, as you won’t have to open two separate command-line interfaces just to get your program running. concurrently is a command that will allow you to run two different commands in one line, while nodemon hot reloads your Node application.

Next, open your client directory and add the following to client's package.json file:

"proxy": "http://localhost:4000"

This proxy setup will enable us to make API calls without having to type the full URL routes; simply typing /api/todos will get all of our todos.

The last setup that is required is to install axios using npm install axios. Axios is a Promise-based HTTP client for your browser so that it could communicate between the front-end and the back-end.

After setting up, we will make our React Components! For this project, we will only need three components: two state components and one stateless. To do this, create a components directory inside your src folder. Your first file will be called Input.js, and it should have the following code:

import React, { Component } from 'react';
import axios from 'axios';
class Input extends Component {
state = {
action: ''
}
addTodo = () => {
const task = {action: this.state.action}
if(task.action && task.action.length > 0){
axios.post('/api/todos', task) //This is where proxy helps
.then(res => {
if(res.data){
this.props.getTodos();
this.setState({action: ""})
}
})
.catch(err => console.log(err))
}else{
console.log("Input field is required");
}
}
handleChange(e) => {
this.setState({
action: e.target.value
})
}
render(){
let { action } = this.state;
return(
<div>
<input type="text" onChange={this.handleChange} value={action} />
<button onClick={this.addTodo}>Add Todo</button>
</div>
)
}
}
export default Input

Next, create a new file, ListTodo.js, and add the following code:

import React from 'react';const ListTodo = ({ todos, deleteTodo }) => {
return(
<ul>
{todos && todos.length > 0 ? (
todos.map(todo=>{return(
<li key={todo._id} onClick={()=>deleteTodo(todo._id)}>{todo.action}</li>
)}):(<li>No Todo(s) left</li>)
}
</ul>
)
}
export default ListTodo

Lastly, in create a Todo.js component and write the following code:

import React, { Component } from 'react';
import axios from 'axios';
import Input from './Input';
import ListTodo from './ListTodo';
class Todo extends Component {
state = {
todos: []
}
componentDidMont(){
this.getTodos();
}
getTodos = () => {
axios.get('/api/todos')
.then(res => {
if(res.data){
this.setState({todos: res.data})
}
})
.catch(err => console.log(err))
}
deleteTodo = (id) => {
axios.delete(`/api/todos/${id}`)
.then(res => {
if(res.data){
this.getTodos()
}
})
.catch(err => console.log(err));
}
render(){
let { todos } = this.state;

return(
<div>
<h1>My Todo List</h1>
<Input getTodos={this.getTodos}/>
<ListTodo todos={todos} deleteTodo={this.deleteTodo} />
</div>
)
}
}
export default Todo;

With our components in order, let us edit our main App.js app inside the client folder into the following:

import React from 'react';import Todo from './components/Todo';
import './App.css';
const App = () => {
return(
<div className="App">
<Todo />
</div>
);
}
export default App;

As you can see, we have imported a CSS file. This is because we will also need to change how the CSS looks. Open App.css and change the entire codebase to the following:

.App {
text-align: center;
font-size: calc(10px + 2vmin);
width: 60%;
margin-left: auto;
margin-right: auto;
}

input {
height: 40px;
width: 50%;
border: none;
border-bottom: 2px #101113 solid;
background: none;
font-size: 1.5rem;
color: #787a80;
}

input:focus {
outline: none;
}

button {
width: 25%;
height: 45px;
border: none;
margin-left: 10px;
font-size: 25px;
background: #101113;
border-radius: 5px;
color: #787a80;
cursor: pointer;
}

button:focus {
outline: none;
}

ul {
list-style: none;
text-align: left;
padding: 15px;
background: #171a1f;
border-radius: 5px;
}

li {
padding: 15px;
font-size: 1.5rem;
margin-bottom: 15px;
background: #282c34;
border-radius: 5px;
overflow-wrap: break-word;
cursor: pointer;
}

@media only screen and (min-width: 300px) {
.App {
width: 80%;
}

input {
width: 100%
}

button {
width: 100%;
margin-top: 15px;
margin-left: 0;
}
}

@media only screen and (min-width: 640px) {
.App {
width: 60%;
}

input {
width: 50%;
}

button {
width: 30%;
margin-left: 10px;
margin-top: 0;
}
}

Lastly, in index.css, add the following items:

body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
background-color: #282c34;
color: #787a80;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

Congratulations! You have created your own To-Do list application using the MERN stack.

Conclusion

The source of this project has got to be one of my favorite articles ever written! This week, I will probably be pushing out several more MERN stack application projects. My plan is to write a final article by the end of the week that details the difficult parts of using MERN stack.

I hope you enjoyed today’s Brew!

--

--

Richard Russell
Cold Brew Code

i like to write about code. i also like cold brew coffee.