Blog Contant

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 57

INTRODUCTION

Blogging has become an essential tool for individuals and organizations alike to share their

thoughts, opinions, and knowledge with the world. With the advancement of technology, creating

and managing a blog has become easier than ever. In this post, we will discuss how to build a

blog post app using React, Node.js and MySQL. React is a popular front-end JavaScript library

for building user interfaces, while Node.js is a powerful back-end JavaScript runtime environment

that can be used with MySQL, a popular open-source relational database management system to

create scalable, robust and efficient web applications.

By combining these technologies, we can create a powerful and dynamic blog post app that will

allow users to create, view, and interact with blog posts. We’ll be creating a database to store

posts and user information using MySQL, we’ll use Node.js and Express to create a server that

interacts with the database. Next, we’ll build a front-end user interface using React, allowing

users to create and edit blog posts. By the end of this tutorial, you will have gained a solid

understanding of how to integrate these technologies to create a fully functional web application.

So let’s get started and learn how to build a blog post app with React, Node.js, and MySQL.

How will the application be?

On the Home page of our blog post application, we will employ a query to retrieve all posts stored
in the MySQL database. Upon clicking a specific post, the application will execute a query to

retrieve the details of the post, including the user who wrote it. This functionality will require a

demonstration of how to join different tables within the MySQL database.

User registration and login features will be implemented and only authorized users, who are the

creators of posts, will be granted editing and deleting permissions. Additionally, the application

will offer to the users the ability to write posts using a rich text editor, allowing the application to

manipulate text style, and upload images to use as post covers.


The application will also provide a sidebar displaying only similar posts to the one being viewed.

This project will help developers in understanding the basic concepts of React and MySQL,

database relationships, user authentication, JSON, web tokens for security, cookies manipulation,

and other essential functionalities.

Quick overview of React and Node.js

React is a popular open-source JavaScript library used for building user interfaces. It was

developed by Facebook and released in 2013. React uses a virtual DOM (Document Object

Model) which is a lightweight copy of the actual DOM that allows React to efficiently update
only the parts of the UI that need to be changed. This approach provides better performance

compared to traditional approaches where the entire DOM is updated.

React’s component-based architecture is another key feature. Components are reusable pieces of

code that define the structure and behavior of a part of the user interface. Components can be

nested within each other, allowing developers to create complex UIs by combining smaller and

simpler components. This approach promotes code reusability, maintainability, and testability.

Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. It allows developers to

write server-side JavaScript code. This enables the use of a single language (JavaScript) for both

client-side and server-side development. Node.js provides non-blocking I/O operations, which
means that I/O operations do not block the event loop and can be executed asynchronously. This

makes Node.js a great choice for building scalable, high-performance web applications.

Node.js also has a large and active community with a wide range of modules and packages

available through the npm registry. These modules and packages provide functionality that can be

easily integrated into Node.js applications, which speeds up development and improves the

overall quality of the code.

Overall, React and Node.js are powerful tools that can be used to build modern web applications.

Their popularity is driven by their ease of use, performance, and active communities.
let’s get started

We will initiate our project by creating a root directory and subsequently adding two

subdirectories named ‘client’ (where React app will be located) and ‘api’ (will be the back-end of

our app) within it.

To avoid creating unnecessary files when setting up a React application and to save time on

cleaning up the project, please refer to the react-mini branch in my GitHub repository, where you

can find a basic project structure to use as a starting point.

With that said, go to the ‘client’ folder and run the following command:
git clone --single-branch -b "react-mini" https://github.com/santiagobedoa/tutorial-

blog_post_app.git .

Once cloned, run npm install to install all dependencies.

Note that the ‘pages’ folder found inside ‘src’ contains some .jsx files that will be the pages that

our application will have, but how we’re going to reach all those pages? The answer is react-

router-dom, but what is it?


react-roueter-dom

React Router DOM is a library that provides routing capabilities to a React application. It is built

on top of the React Router library and provides a higher-level API for building client-side

applications with routing. With React Router DOM, you can easily manage the application state

and change the content displayed on the screen based on the URL path. It provides a set of

components and methods that allow you to define routes and navigate between them using links

or programmatic navigation. It also supports server-side rendering and integration with popular

state management libraries such as Redux.

Example

Here is a simple example of how you can use react-router-dom to create a navigation bar

with links to different pages in your React application:


import { BrowserRouter as Router, Route, Link } from "react-router-
dom";

function Home() {
return <h2>Welcome to the Home page</h2>;
}

function About() {
return <h2>Learn more about us on the About page</h2>;
}

function Contact() {
return <h2>Contact us through the Contact page</h2>;
}

function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>

<Route exact path="/" component={Home} />


<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</div>
</Router>
);
}

export default App;


In this example, we’re using BrowserRouter as the router and Link to create links to

different pages. We're also defining three different page components (Home, About and

Contact) that are rendered when the user navigates to the corresponding URL path.
Note
The Router component is the root component of the routing system. It is responsible for

keeping the current URL in sync with the current set of Route components that are

being rendered.
The Route component is used to declare which components should be rendered for a

specific URL path. It can take a path prop to specify the URL path and

a component prop to specify the component to render for that path.

It’s important to have all Route components inside the Router component so that the

router can match the current URL with the path props of the Route components and

render the corresponding components. If the Route components were not inside

the Router component, the router would not be able to match the current URL with

the path props of the Route components and no components would be rendered.
React-Router-Dom Implementation
Now you know the basics of react-router-dom let’s install it by running npm

install react-router-dom(remember to be located in ‘client’ folder).

Before continuing we’re going to create the navbar and the footer that will be common

components in our pages excluding the login and register page. Let’s create a folder

within ‘src’ named components. Within the folder create two


files Footer.jsx and Navbar.jsx. Add the following code to each file:
// Footer.jsx
import React from "react";

const Footer = () => {


return <div>Footer</div>;
};

export default Footer;


// Navbar.jsx
import React from "react";

const Navbar = () => {


return <div>Navbar</div>;
};

export default Navbar;

Now let’s add the routes to App.js file:

// App.js
// Import the necessary components from the react-router-dom
package and other custom components
import { createBrowserRouter, RouterProvider, Outlet } from "react-
router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Single from "./pages/Single";
import Write from "./pages/Write";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";

// Create a Layout component that defines the structure of the web


page
const Layout = () => {
return (
<>
<Navbar />
<Outlet />
<Footer />
</>
);
};

// Define the application routes and components using the


createBrowserRouter function
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
// Remember that Home, Single post and Write are the pages that
will have a Navbar and a Footer
children: [
{
path: "/",
element: <Home />,
},
{
path: "/post/:id",
element: <Single />,
},
{
path: "/write",
element: <Write />,
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "/register",
element: <Register />,
},
]);

// Define the App function that returns the RouterProvider


component that provides the routing context to the entire app
function App() {
return (
<div className="app">
<div className="container">
<RouterProvider router={router} />
</div>
</div>
);
}

// Export the App component as the default export


export default App;

The code defines a Layout component that provides the basic structure of the web page,

with a navigation bar, content, and a footer. It then defines the application routes using
the createBrowserRouter function from react-router-dom, which takes an array

of objects with path and element properties. The path property specifies the URL path,

while the element property specifies the component to be rendered when the path is
matched. The Layout component is used as a container for the routes, with

the Outlet component used to render the nested routes inside the Layout.

Finally, the App function provides the routing context to the entire app using

the RouterProvider component, which takes the router object as a prop.

The router object contains all the defined routes. The App function returns a div

containing the RouterProvider component wrapped in a container div with the

class container.

Now, if you run your app using npm start and visit http://localhost:3000/ you should

see something like:

Navbar
Home
Footer

Or if you visit http://localhost:3000/write you should see:


Navbar
Write
Footer

With this, I hope you have a clear understanding of how routes and the Outlet

component work.

Basic Front-end

As the primary objective of this tutorial is to illustrate the integration of React, Node.js,

and MySQL to develop a functional application, I will omit the fundamental front-end

creation phase, which is more aligned with HTML and CSS writing practices than React

programming. However, a template for the basic front-end will be available in


the basic-frontend branch of the project repository, which can be used for further

development.

Make sure

 You have modified the following files located


in client/src/pages/: Home.jsx, Login.jsx, Register.jsx, Singl

e.jsx, Write.jsx

 You have modified the following files


located client/src/components/: Footer.jsx, Menu.jsx,Navbar.js

 You have created client/src/style.scss file.

 npm install sass: will be used to style our front-end. But, what

is sass? short for Syntactically Awesome Stylesheets, is a preprocessor

scripting language that is compiled into CSS. It extends the functionality of


CSS by providing a variety of features such as variables, nested rules, mixins,

functions, and more. Sass allows developers to write more maintainable and

scalable stylesheets with less repetition and boilerplate code, and it can be

integrated into a wide range of development workflows, including front-end

frameworks like React, Vue, and Angular. Sass files can be compiled into

regular CSS files that can be used in web applications.

 npm install react-quill: will be used to write articles. But, what

is react-quill? is a React-based rich text editor component that allows

users to format and edit text using a toolbar with a variety of options. It is

built on top of the Quill.js library, and provides a flexible and customizable

interface for creating rich text editors in React applications. With React Quill,

developers can easily add rich text editing capabilities to their applications,

and allow users to create and format text content with ease.

If you start the application ( npm start ) it should look like this:

 http://localhost:3000/login
basic-frontend Login. Jsx

 http://localhost:3000/register
basic-frontend Register. Jsx

 http://localhost:3000/
basic-frontend Home. Jsx

 http://localhost:3000/post/1

basic-frontend Single. Jsx


 http://localhost:3000/write

Before you go any further, I recommend that you stop and review each of the

components that were created and make sure you understand the code, as we will be

working on this code from now on.


Back-end

We will proceed to navigate to the API folder, and initialize a Node application through
the npm in it -y command. Prior to continuing, let's install the necessary libraries

that we will be working with. First, we will install express via npm install

express which will provide us with the ability to create our server. We will also be

utilizing mysql2 by installing it via npm install mysql2, and

finally, nodemon through npm install nodemon to enable auto-refresh upon

changes.
We will modify the script “test” to “start” in the package.json file and set it to

"nodemon index.js" command in order to refresh the server automatically. Additionally,

we will add "types": "modules" in the same file to allow importing libraries in the ES6
module format. package.json should looks like:

{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.1.2",
"nodemon": "^2.0.20"
}
}
Now let’s create index.js file, where we will create our server:

// index.js
import express from "express";

// Create an instance of the Express application


const app = express();

// Use the built-in JSON middleware to parse incoming requests


app.use(express.json());

// Start the server and listen on port 8800


app.listen(8800, () => {
console.log("Connected...");
});

This code sets up an Express application with a JSON middleware and starts a server on
port 8800. The app constant is an instance of the Express application, which is used to

define the routes and middleware for the server. The express.json() middleware is

used to parse incoming JSON data in request bodies. Finally,


the app.listen() method is called to start the server and listen on port 8800.
After executing the npm start command in your terminal, you should see the message
“Connected…” which indicates that the server is listening to incoming requests on the
port 8800 and ready to handle them appropriately.
MySQL Connection

To be able to perform CRUD operations in our Node.js application, we need to establish a

connection with the MySQL database. In order to do that, we will use


the mysql2 library. We will create a new file called db.js inside the API folder to hold

the database connection details.


// db.js
import mysql from "mysql2";

export const db = mysql.createConnection({


host: "localhost",
user: "root",
password: null,
database: "blog_app",
});

In order to establish the connection to the MySQL server, the mysql2 library is used

with a createConnection() method that receives an object with the configuration of

the connection. It is important to note that depending on the configuration of your


MySQL server, the host, user, and password fields may vary. In this example, for

practicality, the root user is being used, which doesn't have a password. However, it's

recommended to create a new user with a password and store this information in
an .env file. This file can be accessed using the dotenv library, which loads the

environment variables stored in the .env file into process.env so that they can be

accessed throughout the application. This is a common practice in production to avoid

exposing sensitive information like database credentials in the source code.

You are probably wondering why the name of the database is “blog_app” if we have never

created it at any time. So let’s create it:

-- setup_mysql.sql
-- create database
CREATE DATABASE IF NOT EXISTS blog_app;
-- create table users
CREATE TABLE IF NOT EXISTS `blog_app`.`users` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`img` VARCHAR(255) NULL,
PRIMARY KEY (`id`)
);
-- create table posts
CREATE TABLE IF NOT EXISTS `blog_app`.`posts` (
`id` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`desc` VARCHAR(1000) NOT NULL,
`img` VARCHAR(255) NOT NULL,
`cat` VARCHAR(255) NOT NULL,
`date` DATETIME NOT NULL,
`uid` INT NOT NULL,
PRIMARY KEY (`id`),
INDEX `uid_idx` (`uid` ASC) VISIBLE,
CONSTRAINT `uid`
FOREIGN KEY (`uid`)
REFERENCES `blog_app`.`users` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
);

The above code is a set of SQL commands that creates a new database named “blog_app”

and two tables: “users” and “posts”.

The “users” table has columns named “id”, “username”, “email”, “password”, and “img”.

The “id” column is set as the primary key for the table.

The “posts” table has columns named “id”, “title”, “desc”, “img”, “cat”, “date”, and “uid”.

The “id” column is set as the primary key and the “uid” column has a foreign key

constraint that references the “id” column in the “users” table. The “INDEX” command

creates an index on the “uid” column for faster querying. The “ON DELETE CASCADE”

and “ON UPDATE CASCADE” commands ensure that any changes to the “users” table

are reflected in the “posts” table.


To create the database and tables, you can execute the setup_mysql.sql file in the

MySQL server. If you're using root user without password, you can run cat

setup_mysql.sql | mysql -u root in the terminal. In case you have set a

password for the root user, you can add the -p flag at the end of the command, which

will prompt you to enter the password.

Route Controller Structure

To modularize our code and improve maintainability, we’ll move away from placing all
routes and functions in index.js. Instead, we’ll create a new directory called “routes”

within the API directory. Inside this directory, we’ll have files for each endpoint of our
application, including auth.js, posts.js, and users.js. Each of these files will

define the routes for the endpoint and the corresponding function to be executed when a

request is made to that route. This approach helps us create specific functions that

perform well-defined tasks, making our code more modular and easier to manage.

Example:

// api/routes/posts.js
import express from "express";

// Creating a new router instance


const router = express.Router();

// Defining a new route on this router for the GET HTTP method for
the '/test' endpoint
// When the '/test' endpoint is reached, the provided callback
function will execute
router.get("/test", (req, res) => {
res.json("this is post");
});

// Exporting this router module so that it can be used elsewhere in


the application
export default router;

Let’s import router to index.js

// api/index.js
import express from "express";
// Import router as postRoutes
import postRoutes from "./routes/posts.js";

// Create an instance of the Express application


const app = express();

// Use the built-in JSON middleware to parse incoming requests


app.use(express.json());

// Routes
app.use("/api/posts", postRoutes);

// Start the server and listen on port 8800


app.listen(8800, () => {
console.log("Connected...");
});

Now if you run the server ( npm start ) and

visit http://localhost:8800/api/posts/test you should get “this is post”. But

as we said before, is important to modularize our code. So we are going to create another

folder inside api named controller that will contain the following
files: auth.js, posts.js, and users.js. Within these files we will have all the

functions in charge of doing the CRUD operations.

Example:
Let’s add the following code to controller/posts.js:
// controller/posts.js
// Exporting a function called testPost that takes two parameters,
req and res, which represent the request and response objects,
respectively.
export const testPost = (req, res) => {
// Sending a JSON response with a string message "this is post
from controller" when this function is called.
res.json("this is post from controller");
};

Modify routes/posts.js:

// routes/posts.js
import express from "express";
import { testPost } from "../controller/posts.js";

// Creating a new router instance


const router = express.Router();

// Defining a new route on this router for the GET HTTP method for
the '/test' endpoint
// When the '/test' endpoint is reached, testPost function will
execute
router.get("/test", testPost);

// Exporting this router module so that it can be used elsewhere in


the application
export default router;

Now if you visit http://localhost:8800/api/posts/test you should get “this is

post from controller” instead “this is post”.

Up to this point, the API folder structure should look like this:
api
├── controller
│ ├── auth.js
│ ├── posts.js
│ └── users.js
├── routes
│ ├── auth.js
│ ├── posts.js
│ └── users.js
├── db.js
├── index.js
├── package.json
└── setup_mysql.sql

Authentication with JWT and Cookie

Now that we have the basic structure of our project. Let’s create our authentication

system. For this we will need to install:

 jsonbwebtoken: in a nutshell, jsonbwebtoken is a compact, URL-safe

means of representing claims to be transferred between two parties. These

claims can be used to authenticate and authorize users in web applications.

The library provides methods for generating, signing, and verifying JSON

Web Tokens (JWTs) using a secret key or public/private key pair.


 crypts: is a JavaScript library used for password hashing. It uses the crypt

algorithm to securely hash passwords and protect them from attacks such as

brute-force and rainbow table attacks. Crypts generates a salt for each

password and then hashes the salted password multiple times, making it

more difficult for an attacker to crack the password even if they have access to

the hashed values. It is commonly used in web development to store user

passwords in a secure manner.


Important! In cryptography, a salt is a random value that is used as an additional

input to a one-way function, such as a cryptographic hash function. The purpose of the

salt is to make it more difficult to perform attacks such as precomputed hash attacks and

dictionary attacks, which try to guess the input of the hash function. By adding a salt to

the input, the resulting hash will be different, even if the input is the same. This makes it

much more difficult for an attacker to crack the hashed password. The salt value is

typically stored alongside the hashed password in a database.


To install this libraries run npm install bcryptjs jsonwebtoken.

Register BACK-END

The first thing we need to do is create a function that allows us to register new users , let’s
add the following function to controller/auth.js file:

// controller/auth.js
import { db } from "../db.js";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";

// This function is responsible for registering a new user in the


database
export const register = (req, res) => {
// CHECK EXISTING USER
// SQL query to check if the user already exists in the database
const query = "SELECT * FROM users WHERE email = ? OR username
= ?";
// Execute the query with the user's email and username as
parameters
db.query(query, [req.body.email, req.body.username], (err, data)
=> {
// Check for errors
if (err) return res.json(err);
// If the query returns data, it means the user already exists,
return a 409 conflict status code
if (data.length) return res.status(409).json("User already
exists!");

// Hash the password and create a user


// Generate a salt value
const salt = bcrypt.genSaltSync(10);
// Generate a hash value using the password and the salt value
const hash = bcrypt.hashSync(req.body.password, salt);

// SQL query to insert the new user in the database


const query = "INSERT INTO users(`username`,`email`,`password`)
VALUES (?)";
// Define the values to be inserted in the query, including the
hashed password
const values = [req.body.username, req.body.email, hash];

// Execute the query with the values as parameters


db.query(query, [values], (err, data) => {
// Check for errors
if (err) return res.json(err);
// If successful, return a 200 status code with a message
return res.status(200).json("User has been created.");
});
});
};

This code exports a function named “register” that handles a POST request for

registering a new user. The function first checks if the user already exists in the database

by executing a SELECT query with the user’s email and username. If the query returns

any data, it means the user already exists, so the function returns a response with a 409

status code and a message “User already exists!”.

If the user does not exist, the function hashes the user’s password using bcrypt library

and creates a new user by executing an INSERT query with the user’s username, email,
and hashed password. If the query is successful, the function returns a response with a

200 status code and a message “User has been created.”

The function uses the db object to execute the SQL queries, which should be established
in a separate file (db.js) to establish a connection with the database.

Let’s add the required routes to perform the authentication, let’s


open routes/auth.js file and add the following code:

// routes/auth.js
// importing express and the necessary controller functions
import express from "express";
import { login, logout, register } from "../controller/auth.js";

// creating a new router instance


const router = express.Router();

// defining routes for register, login, and logout


router.post("/register", register);
router.post("/login", login);
router.post("/logout", logout);

// exporting the router instance


export default router;

Note that login and logout functions haven’t been implemented yet.
Finally, let’s implement authentication routes to our application. index.js file should

look like:

// index.js
// Import the Express library
import express from "express";
// Import routes
import authRoutes from "./routes/auth.js";
import postRoutes from "./routes/posts.js";

// Create an instance of the Express application


const app = express();
// Use the built-in JSON middleware to parse incoming requests
app.use(express.json());

// Routes
app.use("/api/auth", authRoutes);
app.use("/api/posts", postRoutes);

// Start the server and listen on port 8800


app.listen(8800, () => {
console.log("Connected...");
});

The benefits of modularized code become apparent when considering the specific

responsibilities of each function. While it’s possible to centralize database connections,


application routes, and data manipulation functions in index.js, separating each

function improves maintainability by providing clear points of reference in the event of


an issue. It's optimal to create an index.js file to handle the creation of our Express

application with all necessary middleware, a db.js file to establish the database

connection, a routes/ directory to contain all application routes, and

a controller/ directory to house data processing and CRUD operations.


Register FRONT-END

In order to test the registration of a user, we must make some changes in the front-end.
First we need to install axios (npm install axios) to be able to communicate with

the back-end.

Axios is a popular JavaScript library used to make HTTP requests from a web browser or

Node.js. It provides a simple and easy-to-use API for making requests to RESTful web

services, and supports a wide range of features such as sending form data, setting

headers, handling request and response interceptors, and canceling requests. Axios can
be used in both client-side and server-side applications, and is widely used in modern

web development for communicating with APIs and fetching data from servers.
Let’s make the changes to registration page (Register.jsx):

// Register.jsx
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import axios from "axios";

const Register = () => {


const [inputs, setInputs] = useState({
// setting initial state for inputs using useState hook
username: "",
email: "",
password: "",
});

const [err, setError] = useState(null); // setting initial state


for error using useState hook

const navigate = useNavigate(); // using useNavigate hook from


react-router-dom to navigate

const handleChange = (e) => {


// function to handle input changes
setInputs((prev) => ({ ...prev, [e.target.name]: e.target.value
}));
// using spread operator to spread previous state and update
the current input
};

const handleSubmit = async (e) => {


// function to handle form submit
e.preventDefault(); // preventing the default form submission
behavior
try {
await axios.post("/auth/register", inputs); // making a post
request to register the user using axios library
navigate("/login"); // navigating to login page after
successful registration
} catch (err) {
// handling errors if any
setError(err.response.data); // setting error message from
response data
}
};

return (
<div className="auth">
{/* containing the authentication form */}
<h1>Register</h1>
<form>
<input
required
type="text"
placeholder="username"
name="username"
onChange={handleChange} // triggering handleChange
function on input change
/>
<input
required
type="email"
placeholder="email"
name="email"
onChange={handleChange} // triggering handleChange
function on input change
/>
<input
required
type="password"
placeholder="password"
name="password"
onChange={handleChange} // triggering handleChange
function on input change
/>
<button onClick={handleSubmit}>Register</button>{" "}
{/* triggering handleSubmit function on form submit */}
{err && <p>{err}</p>} {/* displaying error message if
there's any */}
<span>
Do you have an account? <Link to="/login">Login</Link>{"
"}
{/* providing link to login page */}
</span>
</form>
</div>
);
};

export default Register; // exporting Register component

When a user enters data into the input fields, the handleChange function updates the

state accordingly.
When the user clicks the “Register” button, the handleSubmit function is triggered. This

function makes an HTTP POST request to the server with the user’s registration

information, and if successful, navigates the user to the login page using the useNavigate

hook. If there’s an error during registration, the error message is displayed on the page.
Finally, we need to add "proxy":http//localhost:8800/api at the end

of client/package.json file. Why? It allows the server to proxy requests to the API

server during development.

This is useful when the React project is hosted on a different server than the API server,

as it allows the frontend code to make requests to the backend API without worrying

about cross-origin resource sharing (CORS) issues.

By specifying the proxy in package.json, requests made to paths beginning with “/api”

will be automatically forwarded to the specified server (in this

case, http://localhost:8800).

Now, if you fill the registration form, you must be redirected to the login page and the

user must be added to the database (“users” table).


Login and Logout

let’s apply the same logic for the login and logout functions. Add this two functions to the
bottom of the controller/auth.js file:
// This function handles user login
export const login = (req, res) => {
// SQL query to check if the user exists in the DB
const query = "SELECT * FROM users WHERE username = ?";

// Execute the query with the provided username


db.query(query, [req.body.username], (err, data) => {
// Handle DB errors
if (err) return res.json(err);

// If no user is found, return an error


if (data.length === 0) return res.status(404).json("User not
found!");

// Check if the password is correct


const isPasswordCorrect = bcrypt.compareSync(
req.body.password,
data[0].password
);

// If the password is incorrect, return an error


if (!isPasswordCorrect)
return res.status(400).json("Wrong username or password!");

// If the login is successful, create a JSON web token


const token = jwt.sign({ id: data[0].id }, "jwtkey");

// Remove the password from the user data


const { password, ...other } = data[0];

// Set the token as a http-only cookie and send user data as


response
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json(other);
});
};

// This function handles user logout


export const logout = (req, res) => {
// Clear the access_token cookie and send a success message
res
.clearCookie("access_token", {
sameSite: "none",
secure: true,
})
.status(200)
.json("User has been logged out.");
};

The login function:

The first step is to construct a SQL query to retrieve the user data from the database
using the provided username, stored in req.body.username. The query variable

stores the SQL statement.


The db.query() method executes the query, with the username as a parameter. The

callback function provides two arguments: err and data. If there is an error, the

function returns a JSON response with the error details.

If the query returns no data, which means that there is no user with the provided

username, the function returns a 404 status with an error message as a JSON response.

If the user is found, the function compares the provided


password, req.body.password, with the hashed password stored in the database,
using the bcrypt.compareSync() method. If the comparison fails, the function

returns a 400 status with an error message as a JSON response.

If the login credentials are correct, the function generates a JSON web token with the
user’s id as payload, using the jwt.sign() method.

Then, the password is removed from the data object using destructuring assignment,

and the user data, excluding the password, is stored in the other variable.
Finally, the generated token is set as an HTTP-only cookie, and the user data is sent as a

JSON response with a 200 status code.


Note: jwt.sign() is a method provided by the JSON Web Token (JWT) package that

is used to create a new JWT. A JWT is a compact and self-contained token that contains

information (i.e., the payload) that can be used to verify the identity of the user.
The jwt.sign() method takes two parameters: the payload and a secret key. The

payload is the data that will be stored in the JWT, and it can be any JavaScript object that

can be serialized as JSON. The secret key is a string that is used to sign the token,
ensuring that the token has not been tampered with.
When the jwt.sign() method is called, it creates a new JWT that includes the payload

and a signature that is generated using the secret key. This JWT is then returned as a

string, which can be sent to the client and stored in a cookie or local storage. When the

client sends a request to the server, the server can verify the identity of the user by

decoding the JWT and checking the payload. If the signature is valid and the payload is

trusted, the server can use the information in the payload to authenticate the user and

respond accordingly.

The logout function:


The function first calls the clearCookie() method on the res (response) object, which

removes the access_token cookie from the user's browser. It takes two arguments: the

name of the cookie to clear ("access_token"), and an object containing additional options
for the cookie. In this case, the options sameSite: "none" and secure: true are

set, which ensure that the cookie is only sent over HTTPS and is not limited to same-site

requests.

cookie-parser

As you may have noticed, we are working with cookies. They are important in web

development for a number of reasons:


1. Session management: Cookies are often used to manage user sessions on

websites. When a user logs in, the website sets a cookie containing a unique

session ID, which is then sent with each subsequent request from the user.

This allows the server to associate subsequent requests with the same session,

and to keep track of user-specific data, such as user preferences or shopping

cart items.

2. Personalization: Cookies can be used to personalize a user’s experience on a

website. For example, a website may use cookies to remember a user’s


language preference or to show personalized recommendations based on the

user’s browsing history.

3. Tracking: Cookies can be used to track user behavior on a website, such as

which pages they visit or which products they view. This data can be used to

improve the website’s user experience, as well as for advertising and

marketing purposes.

Overall, cookies are a powerful tool for web developers to create more personalized and

dynamic web experiences for users, while also providing important functionality such as

session management. They are not as bad as they are painted. As developers we have to

be careful with the third point, because this is when the line of ethics becomes blurred.
In order to work with cookies in our back-end we need to install cookie-parser (npm

install cookie-parser) and specify to the server to use cookie-parser as

middleware:

//index.js
import express from "express";
import authRoutes from "./routes/auth.js";
import postRoutes from "./routes/posts.js";
import cookieParser from "cookie-parser";
// Create an instance of the Express application
const app = express();

// Use the built-in JSON middleware to parse incoming requests


app.use(express.json());
// Use the cookieParser middleware to parse cookies from incoming
requests
app.use(cookieParser());

// Routes
app.use("/api/auth", authRoutes);
app.use("/api/posts", postRoutes);

// Start the server and listen on port 8800


app.listen(8800, () => {
console.log("Connected...");
});

Login FRONT-END

Finally, we need to make some changes to the login page:

import React, { useState } from "react";


import { Link, useNavigate } from "react-router-dom";
import axios from "axios";

// Define a functional component called Login


const Login = () => {
// Use useState hook to create state variables for inputs and
errors
const [inputs, setInputs] = useState({
username: "",
password: "",
});

const [err, setError] = useState(null);

// Use useNavigate hook to create a navigate function


const navigate = useNavigate();
// Define handleChange function to update the input state
variables when the user types into the input fields
const handleChange = (e) => {
setInputs((prev) => ({ ...prev, [e.target.name]: e.target.value
}));
};

// Define handleSubmit function to handle the form submission


when the user clicks the submit button
const handleSubmit = async (e) => {
e.preventDefault();
try {
// Post the user input to the "/auth/login" endpoint and
navigate to the home page
await axios.post("/auth/login", inputs);
navigate("/");
} catch (err) {
// If there is an error, set the error state variable to the
error message
setError(err.response.data);
}
};

// Render the login form with input fields for username and
password and a button to submit the form
return (
<div className="auth">
<h1>Login</h1>
<form>
<input
type="text"
placeholder="username"
name="username"
onChange={handleChange}
/>
<input
type="password"
placeholder="password"
name="password"
onChange={handleChange}
/>
<button onClick={handleSubmit}>Login</button>
{err && <p>{err}</p>}
<span>
Don't you have an account? <Link
to="/register">Register</Link>
</span>
</form>
</div>
);
};

export default Login;

It is not worth explaining what was done since the exact same logic was applied as in the

Register page. Now, if you login with the user previously registered, you must be

redirected to the home page.


React Context API

It is recommended to persist user information in local storage for efficient retrieval

across different React components to present data and enable specific actions. As such,

user data should be stored in local storage to ensure availability and accessibility across

components.
For this purpose, we’re going to use react-context. React context is a feature of React

that allows data to be passed down the component tree without the need to pass props

manually at every level. It provides a way to share data between components, even if they

are not directly related in the component hierarchy. Context is often used for global state

management, such as user authentication and theme settings. It consists of two parts: a

Provider component that provides the data to its children, and a Consumer component

that consumes the data. The useContext hook can also be used to consume context data

in functional components.

To begin, we will create a directory named ‘context’, within our client source code, where

we will store the file ‘authContext.js’. The full path to this file should
be client/src/context/authContext.js. This file will serve as the context for our

application:

// src/context/authContext.js
import axios from "axios";
import { createContext, useEffect, useState } from "react";

// Create a new context called AuthContext


export const AuthContext = createContext();

// Define a component called AuthContextProvider that takes in


children as props
export const AuthContextProvider = ({ children }) => {
// Initialize a state variable called currentUser and set it to
either the user object in local storage or null if it does not
exist
const [currentUser, setCurrentUser] = useState(
JSON.parse(localStorage.getItem("user")) || null
);

// Define a function called login that makes a POST request to


the /auth/login endpoint with the given inputs and sets the
currentUser state variable to the response data
const login = async (inputs) => {
const res = await axios.post("/auth/login", inputs);
setCurrentUser(res.data);
};

// Define a function called logout that makes a POST request to


the /auth/logout endpoint and sets the currentUser state variable
to null
const logout = async (inputs) => {
await axios.post("/auth/logout");
setCurrentUser(null);
};

// Store the currentUser state variable in local storage whenever


it changes
useEffect(() => {
localStorage.setItem("user", JSON.stringify(currentUser));
}, [currentUser]);

// Return the AuthContext.Provider component with the


currentUser, login, and logout functions as values and the children
as its child components
return (
<AuthContext.Provider value={{ currentUser, login, logout }}>
{children}
</AuthContext.Provider>
);
};

This code defines a context for managing authentication in a React application. It exports
an AuthContext object created with the createContext function, which can be used

to provide authentication information to child components in the component tree.


The AuthContextProvider component is also exported, which is a wrapper around

the child components that provides the authentication state and methods for logging in
and logging out. It uses the useState hook to maintain the current user's information

in the component's state. The user's information is initially set by retrieving the stored
user object from the browser's local storage using localStorage.getItem("user").

The login function is defined to perform a login operation by sending a POST request to

the /auth/login endpoint with the user's input data. If the request is successful, the

current user's state is updated with the response data.


The logout function sends a POST request to the /auth/logout endpoint to log out

the user. It then updates the current user's state to null.

The useEffect hook is used to store the current user's information to the browser's

local storage whenever the currentUser state changes.

Finally, the AuthContext.Provider component is returned, which provides

the currentUser, login, and logout values as the context value to its child

components. This enables other components in the application to access the

authentication state and methods.


Since we have functions in charge of doing the login and logout in the app using context,
we must make some changes in Login.jsx:

//useContext hook to get the login function from the AuthContext.

const { login } = useContext(AuthContext);

// Define handleSubmit function to handle the form submission


when the user clicks the submit button
const handleSubmit = async (e) => {
e.preventDefault();
try {
// Post the user input to the "/auth/login" endpoint and
navigate to the home page
await login(inputs); // new login function
navigate("/");
} catch (err) {
// If there is an error, set the error state variable to the
error message
setError(err.response.data);
}
};

Last, we need to ensure that our application is wrapped with the Provider component so

that the components can access the state data and actions provided by the context.

Specifically, the App component will be the child component of the Provider component.

import React from "react";


import ReactDOM from "react-dom/client";
import App from "./App";
import { AuthContextProvider } from "./context/authContext";

const root = ReactDOM.createRoot(document.getElementById("root"));


root.render(
<React.StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</React.StrictMode>
);

Let’s apply the same logic to the Navbar:

// Navbar.jsx
import React, { useContext } from "react";
import { Link, useNavigate } from "react-router-dom";
import { AuthContext } from "../context/authContext";
import Logo from "../images/logo.png";

const Navbar = () => {


const { currentUser, logout } = useContext(AuthContext);
const navigate = useNavigate();

const logoutNavbar = () => {


logout();
navigate("/login");
};

return (
<div className="navbar">
<div className="container">
<div className="logo">
<a href="/">
<img src={Logo} alt="logo" />
</a>
</div>
<div className="links">
<Link className="link" to="/?cat=art">
<h6>ART</h6>
</Link>
<Link className="link" to="/?cat=science">
<h6>SCIENCE</h6>
</Link>
<Link className="link" to="/?cat=technology">
<h6>TECHNOLOGY</h6>
</Link>
<Link className="link" to="/?cat=cinema">
<h6>CINEMA</h6>
</Link>
<Link className="link" to="/?cat=design">
<h6>DESIGN</h6>
</Link>
<Link className="link" to="/?cat=food">
<h6>FOOD</h6>
</Link>
<span>{currentUser?.username}</span>
{currentUser ? (
<span onClick={logoutNavbar}>Logout</span>
) : (
<Link className="link" to="/login">
Login
</Link>
)}
<span className="write">
<Link className="link" to="/write">
Write
</Link>
</span>
</div>
</div>
</div>
);
};

export default Navbar;

Now if you login, you should see the username in the navbar, and if you logout you must

be redirected to the login page and localstorage should be empty.


Fetch MySQL data

Now we will retrieve information about the posts. However, before proceeding, I would

like to provide you with an SQL script to insert data into the database. I recommend to

delete any pre-existing data to avoid conflicts. Note that the password in the script is

already encrypted. To login use the password “test” which was used for the two users.
-- data_mysql.sql
-- add users
INSERT INTO `blog_app`.`users` (`id`, `username`, `email`,
`password`, `img`) VALUES ('1', 'Santiago', '[email protected]',
'$2a$10$VqZJYEYnauC8qrVrVpWZX.E0u95i40pBmAEOc.Vs178nUJNyenzaa',
'https://images.unsplash.com/photo-1633332755192-727a05c4013d?
ixlib=rb-
4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format
&fit=crop&w=1760&q=80');
INSERT INTO `blog_app`.`users` (`id`, `username`, `email`,
`password`, `img`) VALUES ('2', 'Pablo', '[email protected]',
'$2a$10$VqZJYEYnauC8qrVrVpWZX.E0u95i40pBmAEOc.Vs178nUJNyenzaa',
'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?
ixlib=rb-
4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format
&fit=crop&w=1760&q=80');

-- insert posts
insert into `blog_app`.`posts` (`id`, `title`, `desc`, `img`,
`uid`, `cat`, `date`) VALUES ('1', 'Lorem ipsum dolor sit amet
consectetur adipisicing elit', 'Lorem, ipsum dolor sit amet
consectetur adipisicing elit. A possimus excepturi aliquid nihil
cumque ipsam facere aperiam at! Ea dolorem ratione sit debitis
deserunt repellendus numquam ab vel perspiciatis corporis!',
'https://images.unsplash.com/photo-1513364776144-60967b0f800f?
ixlib=rb-
4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format
&fit=crop&w=2671&q=80', '1', 'art', '2023-02-08');
insert into `blog_app`.`posts` (`id`, `title`, `desc`, `img`,
`uid`, `cat`, `date`) VALUES ('2', 'Lorem ipsum dolor sit amet
consectetur adipisicing elit', 'Lorem, ipsum dolor sits amet
consectetur adipisicing elit. A possimus excepturi aliquid nihil
cumque ipsam facere aperiam at! Ea dolorem ratione sit debitis
deserunt repellendus numquam ab vel perspiciatis corporis!',
'https://images.unsplash.com/photo-1536924940846-227afb31e2a5?
ixlib=rb-
4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format
&fit=crop&w=1766&q=80', '2', 'food', '2023-02-08');
insert into `blog_app`.`posts` (`id`, `title`, `desc`, `img`,
`uid`, `cat`, `date`) VALUES ('3', 'Lorem ipsum dolor sit amet
consectetur adipisicing elit', 'Lorem, ipsum dolor sit amet
consectetur adipisicing elit. A possimus excepturi aliquid nihil
cumque ipsam facere aperiam at! Ea dolorem ratione sit debitis
deserunt repellendus numquam ab vel perspiciatis corporis!',
'https://images.unsplash.com/photo-1546069901-ba9599a7e63c?
ixlib=rb-
4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format
&fit=crop&w=1180&q=80', '2', 'art', '2023-02-08');
insert into `blog_app`. `posts` (`id`, `title`, `desc`, `img`,
`uid`, `cat`, `date`) VALUES ('4', 'Lorem ipsum dolor sit amet
consectetur adipisicing elit', 'Lorem, ipsum dolor sits amet
consectetur adipisicing elit. A possimus excepturi aliquid nihil
cumque ipsam facere aperiam at! Ea dolorem ratione sit debitis
deserunt repellendus numquam ab vel perspiciatis corporis!',
'https://images.unsplash.com/photo-1493770348161-369560ae357d?
ixlib=rb-
4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format
&fit=crop&w=1770&q=80', '1', 'food', '2023-02-08');

Now we have two users and some posts created in the database we can proceed. First let’s

add the routes that we are going to use:

// api/routes/posts.js
import express from "express";
import {
add Post,
delete Post,
get Post,
get Posts,
update Post,
} from "../controller/posts.js";

// Create a new Router object


const router = express.Router();

// Define routes for various HTTP methods and their corresponding


functions
router.get("/", get Posts); // Get all posts
router.get("/:id", get Post); // Get a specific post by its ID
router.post("/", add Post); // Add a new post
router. Delete ("/:id", delete Post); // Delete a post by its ID
router. Put ("/:id", update Post); // Update a post by its ID
export default router;

Now let’s create the logic in controller/posts.js:

import {db} from "../db.js";


import jwt from "jsonwebtoken";

// Retrieves posts from a database


export const get Posts = (req, res) => {
// If the query string includes a category parameter,
// select all posts from the given category. Otherwise,
// select all posts.
const q = req.query.cat
? "SELECT * FROM posts WHERE cat=?"
: "SELECT * FROM posts";

// Use the database object to query the database with the


// appropriate SQL statement and any necessary parameters.
db.query(q, [req.query.cat], (err, data) => {
// If there's an error, send a 500 status code and the error
message
if (err) return res.status(500).send(err);

// Otherwise, send a 200 status code and the data as JSON


return res.status(200).json(data);
});
};

// Retrieves a single post from the database


export const get Post = (req, res) => {
// Select specific fields from both the users and posts table,
// and join them based on the user ID of the post author.
const q =
"SELECT p.id, `username`, `title`, `desc`, pig, using AS
ushering, `cat’, ‘date` FROM users u JOIN posts p ON u.id = pied
WHERE p.id = ?";

// Use the database object to query the database for the post
with
// the given ID, and any necessary parameters.
db.query(q, [req.params.id], (err, data) => {
// If there's an error, send a 500 status code and the error
message
if (err) return res.status(500).json(err);

// Otherwise, send a 200 status code and the first item in the
data array as JSON
return res.status(200).json(data[0]);
});
};

// Adds a new post to the database


export const add Post = (req, res) => {
// Check if the user is authenticated by checking for a token in
the cookies
const token = req.cookies.access_token;
if (!token) return res.status(401).json("Not authenticated!");

// Verify the token using the secret key


jwt. Verify(token, "jwtkey", (err, user Info) => {
// If there's an error, the token is not valid
if (err) return res.status(403).json("Token is not valid!");

// Otherwise, construct the SQL query to insert a new post into


the database
const q =
"INSERT INTO posts(`title`, `desc`, `img`, `cat`,
`date`,`uid`) VALUES (?)";

// Define an array of values to be inserted into the database,


including the
// post data from the request body and the user ID from the
decoded token
const values = [
req.body.title,
req.body.desc,
req.body.img,
req.body.cat,
req.body.title,
userInfo.id,
];
// Use the database object to execute the SQL query with the
values array
db.query(q, [values], (err, data) => {
// If there's an error, return a 500 status code and the
error message
if (err) return res.status(500).json(err);

// Otherwise, return a 200 status code and a success message


return res.json("Post has been created.");
});
});
};

// Deletes a post from the database


export const delete Post = (req, res) => {
// Check if the user is authenticated by checking for a token in
the cookies
const token = req.cookies.access_token;
if (!token) return res.status(401).json("Not authenticated");

// Verify the token using the secret key


jwt. Verify(token, "jwtkey", (err, user Info) => {
// If there's an error, the token is not valid
if (err) return res.status(403).json("Token is not valid");

// Otherwise, get the ID of the post to be deleted from the


request parameters
const postId = req.params.id;

// Construct an SQL query to delete the post with the specified


ID, but only if
// the user ID associated with the post matches the ID of the
authenticated user
const q = "DELETE FROM posts WHERE `id` = ? AND `uid` = ?";

// Execute the SQL query with the postId and userInfo.id as


parameters
db.query(q, [postId, userInfo.id], (err, data) => {
// If there's an error, return a 403 status code and an error
message
if (err) return res.status(403).json("You can delete only
your post");

// Otherwise, return a 200 status code and a success message


return res.json("Post has been deleted");
});
});
};

// Update a post
export const updatePost = (req, res) => {
// Get the access token from the request cookies.
const token = req.cookies.access_token;

// Check if the token exists, if not, return an error response.


if (!token) return res.status(401).json("Not authenticated!");

// Verify the token using the "jwtkey" secret key. If the token
is not valid, return an error response.
jwt. Verify (token, "jwtkey", (err, user Info) => {
if (err) return res. status (403). json ("Token is not
valid!");

// Get the post ID from the request parameters.


const postId = req.params.id;

// SQL query to update the post with new values.


const q =
"UPDATE posts SET `title`=? `desc`=? `img`=? `cat`=? WHERE
`id` = ? AND `uid` = ?";

// An array containing the new values for the post.


const values = [req.body.title, req.body.desc, req.body.img,
req.body.cat];

// Execute the query using the values and post ID. If there's
an error, return an error response. Otherwise, return a success
response.
db.query(q, [...values, postId, userInfo.id], (err, data) => {
if (err) return res.status(500).json(err);
return res.json("Post has been updated.");
});
});
};

This code defines several functions to interact with a database.


The get Posts function retrieves all posts from the database, or only those from a

specific category if a category parameter is provided in the request.


The get Post function retrieves a single post from the database based on its ID, along

with information about the user who created it.


The add Post function adds a new post to the database, but only if the user is

authenticated with a valid JSON Web Token (JWT).


The delete Post function deletes a post from the database, but only if the

authenticated user is the one who created the post.


The updatePost function updates an existing post in the database, but only if the

authenticated user is the one who created the post. Like add Post and delete Post,

this function also requires a valid JWT for authentication.


Home page

Given that we have established endpoints within our backend to fetch post data, the next

step is to incorporate these endpoints into the Home page component in order to retrieve

data from the database and display it accordingly.

// Home. Jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import { Link, use Location } from "react-router-dom";

const Home = () => {


// Declaring a state variable called posts and initializing it to
an empty array
const [posts, set Posts] = useState([]);

// Getting the current URL query string (if any) using the use
Location hook from react-router-dom
const cat = use Location().search;

// Defining an effect that runs when the cat variable changes


useEffect(() => {
// Defining an asynchronous function called fetch Data
const fetch Data = async () => {
try {
// Making an HTTP GET request to the server to retrieve
posts data based on the cat variable
const res = await axios. Get(`/posts${cat}`);
// Updating the posts state variable with the retrieved
data
set Posts(res.data);
} catch (err) {
// Logging any errors that occur during the request
console.log(err);
}
};
// Calling the fetch Data function
fetch Data();
}, [cat]); // Specifying that this effect should only run when
the cat variable changes

// Defining a helper function called get Text that takes an HTML


string and returns the text content
const get Text = (html) => {
const doc = new DOM Parser().preformatting(html, "text/html");
return doc.body.textContent;
};

// Rendering the Home component


return (
<div className="home">
<div className="posts">
{/* Mapping over the posts state variable and rendering a
Post component for each post */}
{posts. Map((post) => (
<div className="post" key={post.id}>
<div className="post-img">
{/* Rendering the post image */}
<img src={`${post.img}`} alt="post cover" />
</div>
<div className="content">
{/* Rendering a link to the post page */}
<Link className="link" to={`/post/${post.id}`}>
<h1>{post.title}</h1>
</Link>
{/* Rendering the post description */}
<p>{get Text(postdocs)}</p>
{/* Rendering a button to read more */}
<Link className="link" to={`/post/${post.id}`}>
<button>Read More</button>
</Link>
</div>
</div>
))}
</div>
</div>
);
};

export default Home;

This is a React component that displays a list of blog posts. The component retrieves data

from a server based on the current URL query string and uses state to store and render

the retrieved data. It also defines a helper function to extract text content from HTML
strings. The component maps over the retrieved data and renders a Post component for

each post, which includes the post image, title, description, and a link to read more.

If you visit the “art” section you should see only the posts that contain the “art” category,
the same should happen for the “food” category:

Art:
Food:
The useState hook initializes a state variable called post to an empty object and provides

a function called setPost to update the state. The useLocation hook returns the current

location object, which contains information about the current URL. The useNavigate

hook returns a navigate function that can be used to navigate to a new location. The

useContext hook is used to get the current user from the AuthContext. The useEffect
hook is used to fetch the blog post data from the server when the component mounts.

The handleDelete function is used to delete a blog post. The getText function is a helper

function to extract plain text from an HTML string. Finally, the component returns a JSX

element that renders the blog post content, including the post image, author and date

information, edit and delete buttons, and post title and description.
The moment library (npm install moment) was used to get the current date to insert

into the DB. Note: the description found in the database is in quotation marks and italics.

The paragraphs with Lorem Ipsum were left to fill out the information page (aesthetic
purposes only).
Recommended posts
Let’s modify Menu.jsx:

// Menu.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";

// Defining a functional component named Menu which takes a single


prop named cat
const Menu = ({ cat }) => {
// Initializing posts state with an empty array using useState
hook
const [posts, setPosts] = useState([]);

// useEffect hook is used to fetch posts related to the category


useEffect(() => {
// Defining an async function fetchData to fetch posts related
to the category using axios
const fetchData = async () => {
try {
const res = await axios.get(`/posts/?cat=${cat}`);
setPosts(res.data);
} catch (err) {
console.log(err);
}
};
// Calling fetchData function to fetch data when component is
mounted or when category is changed
fetchData();
}, [cat]);

return (
<div className="menu">
<h1>Other posts you may like</h1>
{posts.map((post) => (
<div className="post" key={post.id}>
<img src={`${post.img}`} alt="post cover" />
<h2>{post.title}</h2>
{/* Using Link component to navigate to the post */}
<Link className="link" to={`/post/${post.id}`}>
<button>Read More</button>
</Link>
</div>
))}
</div>
);
};

export default Menu;

As you can see, the logic applied to this component is quite similar to the one we have

used for the others. We utilize the useEffect hook to invoke axios for data fetching and

employ the useState hook (posts, setPosts) to retain the response. Subsequently, we
execute the rendering of posts, pertaining to a specific category, from the “posts”

variable.”

Write post

Before we dive into implementing the article writing feature, let’s first decide how we

want to save the images that users attach to their posts. There are several options we

could use, like cloud storage services, but for this tutorial, we’re going to keep it simple

and show you how to upload your images directly to your server.
Let’s install multer (npm install multer) which is a popular middleware for

handling file uploads in Node.js. Multer is used in conjunction with Express.js and allows

us to easily handle multipart/form-data, which is typically used when uploading files

through HTML forms. With Multer, we can easily specify where to save uploaded files,

limit the size of files that can be uploaded, and perform various validations on the files

being uploaded.
Let’s add multer to index.js in the api server:

// ape/index.js

// Use the milter middleware to storage images


// Configuration object for setting destination and filename for
the uploaded file
const storage = multer.diskStorage({
destination: function (req, file, cob) {
// Set the destination folder where the uploaded file should be
stored
cb(null, "../client/public/upload");
},
filename: function (req, file, cb) {
// Set the filename of the uploaded file
cb(null, Date. Now() + file.originalname);
},
});

// Set up multer middleware with the defined storage configuration


const upload = multer({ storage });

// Set up a POST endpoint for handling file uploads


app.post("/api/upload", upload.single("file"), function (req, res)
{
// Get the uploaded file
const file = refile;
// Send a response with the filename of the uploaded file
restates(200).json(file. Filename);
});
It is crucial to utilize the use Location hook in order to determine the current state of the

application. This is especially important when editing an existing post, as


the selection().state will provide us with the post ID as well as other pertinent

information such as the post’s title and description. By utilizing this information, we can

distinguish whether we are creating a new post or modifying an existing one.


Finally, we should change ${post.img} to ../upload/${post.img} in your Home,

Single and Menu components to properly render the images that because we are using

multer.
What’s next?

This tutorial provides a comprehensive guide on integrating React, Node.js, and MySQL

to develop a full-stack application. It covers the deployment of a server with crucial

middleware, routes, and controllers using the Express framework. You have learned how

to establish a connection from the server to the database and perform CRUD (Create,

Read, Update, Delete) operations from the client to the server.

Great job on completing the tutorial! Now, if you want to take your app to the next level,

here’s a fun challenge for you: add a feature that allows users to upload a profile image

when they register. You could also create a user panel where users can edit their profile
information such as their name, email, password, and profile picture. Don’t worry if

you’re not sure where to start, the route and user controller are ready for you to get

creative with! Have fun!


Important

This project was created by “Lama Dev,” a passionate developer who shares valuable

insights and tutorials on their YouTube channel. You can watch the full tutorial series

that inspired this blog post here.

It’s essential to understand that this blog and the associated code were developed with a

primary focus on learning and educational purposes. I believe that one of the best ways
to truly grasp and internalize knowledge is by explaining it to others. In that spirit, this

blog serves as a comprehensive guide to integrating React, Node.js, and MySQL to build

a full-stack application.

You might also like