Full Stack: ToDo App with MySQL, Node.js, Express and React
So I had two goals for this summer, Build a full stack app and make a React Native app. Using a stack of MySQL, Node.js, Express and React I managed to build a full stack ToDo app! In this blog post I’ll show you how I did it!
The whole app works like this: I have a MySQL database where I store all my ToDos. Using Node.js and Express (a web application framework) I made a REST API that will allow my React front end to communicate with the MySQL database. And, of course, React as the front end.
Here’s how the React front end app ended up looking!
So, for the SQL server I could use either MySQL or MongoDB. I decided to use MySQL since I already have a lot of experience with it through school. MySQL is maintained by Oracle, and is both open-sourced and free to use! MySQL runs in the background of your computer and you can give it commands through the CMD to manage your database, but I strongly recommend downloading MySQL Workbench so you can get a nice GUI to use instead. I won’t go into depth about how I made my database here.

Now, for the API I decided to use Node.js and Express. They are also both free to use and very popular. For those that don’t know, Node.js is JavaScript runtime built using Chrome’s V8 JavaScript Engine. Basically, JavaScript usually only runs through a browser, but with Node.js we can run it without one. Express is a web app framework for Node.js. It makes creating APIs so much easier.
So first up is the entry point “server.js” file:
// Init const express = require('express'); const bodyParser = require("body-parser"); const cors = require('cors'); // Set up a const for express and a port const app = express(); const port = 3000; // Init connection to database const db = require('./db'); // Middleware app.use(cors()); app.use(bodyParser.json()); // Routers const todoRouter = require('./routes/todo.route'); // Use routers app.use('/todo', todoRouter); // Start listening/server app.listen(port, () => console.log(`ToDo API listening on port ${port}...`));
First I import express itself, body-parser (used for taking the body information from a POST and making it accessible for the API) and CORS (which is used for being able to skip the Same-origin policy, which means we can connect with remote hosts to the API). Next is setting up express and a port to use with it. After that I set up something called “Middleware”. They are basically functions that gets called when the API receive a request and can manipulate the request object from the user and the response object from the API. We have to set both cors and body-parser as middleware for them to work. Next, I set up “routers” for my todos. I need to research routers a little more, but they let me organize my routes into a different file.
For connecting the API to my database I have a file called “db.config.js” which handles the settings:
module.exports = { HOST: "localhost", USER: "root", PASSWORD: "PASSWORD", DB: "tododb" };
And then a “db.js” for connecting to the database:
const mysql = require("mysql"); const dbConfig = require("./config/db.config.js"); // Create a connection to the database const connection = mysql.createConnection({ host: dbConfig.HOST, user: dbConfig.USER, password: dbConfig.PASSWORD, database: dbConfig.DB }); // open the MySQL connection connection.connect(error => { if (error) throw error; console.log("Successfully connected to the database!..."); }); module.exports = connection;
Here I just create a connection using a MySQL library for Node.js, and export it so that the route files can use it.
Last up is the route file for ToDo. This is the code that’s gonna handle the HTTP request and responses for the ToDos. I’m not gonna show the entire file since it’s quite big, but all of the functions looks quite the same. It can be viewed through the GitHub page.
const express = require('express'), router = express.Router(), db = require('../db'); // Get one ToDo router.get('/:todoId', function (req, res) { let sql = `SELECT * FROM todo WHERE id = ${req.params.todoId}`; db.query(sql, function (err, data, fields) { if (err) return res.status(400).send(err); if(Object.keys(data).length === 0) return res.status(404).send(`404: ToDo with id: ${req.params.todoId} not found.`); res.json({ data, message: "ToDo retrieved successfully" }) }); });
First, I’m importing express, the router and the db (database connection). I can now add functions that handles “Get”, “POST”, “Put” and “Delete” requests for my ToDos. In the code above I handle a Get request for getting a single specific ToDo from the database. Using “db” I can send a query to my database and use it in the callback function. From the callback function I can check if it’s empty (and in case return a HTTP status code 404: Not found) or send the data it found as a JSON object back to the React client.
And that’s it for the back-end. My API isn’t the best right now, but I’m planning to add JWT security and rewrite it so that it uses the information from the body of an HTTP request.
Now for the front-end. I’ve covered making React apps some times now, so for this blog post I’m only gonna focus on what’s related to the full-stack part.
First, in my constructor I add a var for my API Url:
Then when my App starts I run a function called "getTodos" which sends a HTTP request to my API and (hopefully) receives some ToDos back, and sets the state:constructor(props) { super(props) this.apiURL = "http://localhost:3000"; this.state = { todos: [] } }
// When the app starts, get todos from the database and set it to the state componentDidMount() { this.getTodos(); } getTodos() { fetch(`${this.apiURL}/todo`) .then(res => res.json()) .then((data) => { let todos = data.data; // Convert 'completed' from 0's to bools for (let i = 0; i < todos.length; i++) { todos[i].completed = (todos[i].completed === 0) ? false : true; } // Set todos in the state this.setState({ todos }) }) .catch(console.log) }
Since MySQL doesn't support straight up boolean values, I use 0's and 1's as false and true. That's why I convert the "completed" value from those 0's and 1's to bool's.
Now for adding a new ToDo:
// Adds a new ToDo addToDo = desc => { fetch(`${this.apiURL}/todo?desc="${desc}"&completed=0`, { "method": "POST" }) .then(res => res.json()) .then((data) => { //Refresh ToDos this.getTodos(); }) .catch(console.log) }
I send a POST request with the new ToDos description in the query and a completed value of zero. The id is being set server side, so my client app doesn't need to set it itself. In the callback function I could receive the id back and just add a new ToDo object to my array, but I decided to take the lazy way and just refresh all ToDos from my database.
The delete and update (setAsComplete) functions are quite similiar:
// Marks a ToDo as completed setAsCompleted = id => { // Get the selected todo let editedToDo = this.state.todos.find(todo => todo.id === id); // Flip the completed value and return it as a number editedToDo.completed = editedToDo.completed ? 0 : 1; fetch(`${this.apiURL}/todo?id=${editedToDo.id}&desc="${editedToDo.desc}"&completed=${editedToDo.completed}`, { "method": "PUT" }) .then(res => res.json()) .then((data) => { //Refresh ToDos this.getTodos(); }) .catch(console.log) } // Removes ToDo removeToDo = id => { fetch(`${this.apiURL}/todo/${id}`, { "method": "DELETE" }) .then(res => res.json()) .then((data) => { //Refresh ToDos this.getTodos(); }) .catch(console.log) }
And that's it! A full working full-stack ToDo application!

My next plan is to port over my client React app to React Native, so stay tuned for that!