DEV Community

Cover image for Blog Server with JWT Authentication - Powered by Node.js & MySQL
Vitor Alecrim
Vitor Alecrim

Posted on • Edited on

Blog Server with JWT Authentication - Powered by Node.js & MySQL

Introduction

Hello, how are you? I’m Vitor, and I’m back with a new project. It’s been some time since my last tutorial, as I’ve been busy with other activities over the past few months. However, it’s time to return to the fascinating world of code.

Today, we’ll build a Node.js server for a blog platform. Our server will have the ability to register users and authenticate them using JWT. Additionally, we’ll cover topics such as registering, querying, editing, and deleting data like text and images in a MySQL database.

Once your API is ready, you’ll be able to power your frontend application. If you don’t have a site yet, check out the frontend tutorial*.

Throughout this tutorial, I’ll strive to be as concise and clear as possible.

You can access the source code here:

GitHub logo Gondrak08 / blog-nodejs-server

A blog server made with Node.js and Mysql. This is part of a tutorial project.

Servidor para Blog - Node.js & Mysql.

Ingredientes

  • bcryptjs - uma biblioteca de criptografia para senhas.
  • cors - uma middleware que nos permite lidar com Cross-Origin Resource Sharing (CORS).
  • dotenv - um módulo que nos possibilita acessar variáveis em arquivos '.env'.
  • express - uma biblioteca que nos permite criar APIs RESTful.
  • jsonwebtoken - uma biblioteca para a criação dos nossos tokens de autenticação.
  • multer - o Multer é uma biblioteca que nos permitirá salvar imagens em nosso servidor.
  • mysql - uma biblioteca de conexão com o banco de dados MySQL.

Como usar

Instalando aplicação.

npm i

Conectando ao Banco de dados

Lembre-se de configurar a sua conexão ao banco de dados Mysql. Em connection.js

const mysql = require("mysql");
const dbConfig = {
  host: "localhost",
  user: "your db user",
  password: "your db password",
  database: "your
Enter fullscreen mode Exit fullscreen mode

I hope you enjoy it.

Happy coding!

Libraries

Here are the libraries we’ll use in our application:

  • bcryptjs - a library for password encryption.
  • cors - middleware that allows us to handle Cross-Origin Resource Sharing (CORS).
  • dotenv - a module that lets us access variables in .env files.
  • express - a library that allows us to create RESTful APIs.
  • jsonwebtoken - a library for creating authentication tokens.
  • multer - Multer is a library that allows us to save images to our server.
  • mysql - a library for connecting to a MySQL database.

Architecture

The folder and file structure of our application:

server
  |
  |- middleware/
  |      |- auth.js
  |      |- upload.js
  |
  |- routes/
  |      |- user.js
  |      |- article.js
  |
  |- uploads/
  |- utils/
  |      |- trucantText.js
  |
  |- connection.js
  |- server.js
  |- tables.sql
Enter fullscreen mode Exit fullscreen mode

Database

Before proceeding with our application, it is necessary to create the database. We will use the MySQL database to develop our project. You can choose the database management platform you are most familiar with, such as DBeaver, among others.

In my case, I will use the terminal:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 14
Server version: 8.0.35-0ubuntu0.22.04.1 (Ubuntu)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

Enter fullscreen mode Exit fullscreen mode

Once inside your application, create the database:

mysql> create database blog_db;
Enter fullscreen mode Exit fullscreen mode

Table Declaration

Next, we will write the tables that will be used in our database.

If you are using the terminal, access your database:

mysql> use blog_db;
Enter fullscreen mode Exit fullscreen mode

Once inside your database, copy each table below and paste it into your terminal.

  • 1 User
create table user(
    id int primary key AUTO_INCREMENT,
    name varchar(250),
    password varchar(250),
    email varchar(50),
    role varchar(50),
    UNIQUE(email)
);
Enter fullscreen mode Exit fullscreen mode
  • 2 Articles
create table articles(
    id int NOT NULL AUTO_INCREMENT,
    userId int NOT NULL,
    title varchar(250) NOT NULL,
    image varchar(250) DEFAULT NULL,
    content varchar(250) NOT NULL,
    primary key(id)
);
Enter fullscreen mode Exit fullscreen mode
  • 3 RefreshToken
create table refreshToken(
    id int primary key AUTO_INCREMENT,
    expiresIn int,
    userName varchar(250),
    userId int NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

The final result of our tables will be:

mysql> show tables;
+-------------------------+
| Tables_in_blog_db |
+-------------------------+
| articles                |
| refreshToken            |
| user                    |
+-------------------------+
3 rows in set (0.00 sec)

Enter fullscreen mode Exit fullscreen mode

Finally, let's create a test user with the following command inside the database:

 mysql> insert into user(name, password, email, role) values ('admin','admin','admin@admin.com','admin');
Enter fullscreen mode Exit fullscreen mode

First Steps

Starting the Application

Let's start by creating the package.json. In your terminal, run the following command:

npm init
Enter fullscreen mode Exit fullscreen mode

Configure your npm init as you see fit.

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (test) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
Enter fullscreen mode Exit fullscreen mode

Installing dependencies

Next, let's install the libraries.

npm i express multer mysql bcryptjs cors dotenv jsonwebtoken
Enter fullscreen mode Exit fullscreen mode

Creating the server

Inside the root of the project, create the server.js file and add the following code:

const express = require("express");
const cors = require("cors");
// connection to the server
const dbConnection = require("./connection");
const router = require("./routes/user");
// import routs
const user = require("./routes/user");
const article = require("./routes/article");

const app = express();

// middleware
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use("/uploads", express.static("uploads"));
app.use(express.json());
const upload = require("./middleware/upload");
// declare routs
app.use("/user", user);
app.use("/articles", article);

// calling server
const PORT = 8080;
app.listen(PORT, () => {
  {
    console.log(`server is running at port ${PORT} `);
  }
});
Enter fullscreen mode Exit fullscreen mode

It is possible that some of the files have not been written yet at this point, but don't worry. We will create them later.

Once all the necessary modules are installed and the connection to the database is correctly configured, before starting the server:

  • If you want to do a test, comment out all the routes and database connection code, leaving only the app.listen.

You can write the following in the terminal:

$ node server.js
Enter fullscreen mode Exit fullscreen mode

And our server will run on port 8080.

Connection to the Data Base

Using the mysql library, let's write the code that will connect our server to the database.
Remember to replace the values with your exact details to connect to the database. You can store them in your .env file for better protection.

Create a file named connection.js with the following content:

const mysql = require("mysql");
const dbConfig = {
  host: "localhost",
  user: "your db user",
  password: "your db password",
  database: "your db",
};
const dbConnection = mysql.createConnection(dbConfig);
module.exports = dbConnection;
Enter fullscreen mode Exit fullscreen mode

Middleware

Inside the middleware folder, we will write two files.

Authentication

Inside the middleware/auth.js file, we will write the system responsible for validating our authentication key. This function will be used to protect the private routes of our application, preventing unauthorized users from accessing our database data.

First, in our root folder, let's declare a .env file. Then, within this file, we will save our authentication key.

TOKEN_KEY = "ColoqueAquiOSeuToken"
TOKEN_REFRESH = "InsiraAquiSeuTokenDeVaLiDaçÃo"
TOKEN_LIFE="300" // tempo de vida do token
TOKEN_LIFE_REFRESH="400" // tempo de vida do token refresh
Enter fullscreen mode Exit fullscreen mode

You can use the terminal itself to generate your token. First, enter node.

$ node
Enter fullscreen mode Exit fullscreen mode

Next, using the crypto library:

> require("crypto").randomBytes(64).toString('hex')
Enter fullscreen mode Exit fullscreen mode

The result will be a line composed of numbers and letters.

If you want to learn more about jwt, check out its documentation.

Now, let's write our verifyToken function inside the auth.js file:

const jwt = require("jsonwebtoken");
const config = process.env;

const verifyToken = (req, res, next) => {
  const token =
    req.body.token || req.query.token || req.headers["x-access-token"];
  if (!token)
    return res.status(403).send("a token is required for authentication");
  try {
    const decoded = jwt.verify(token, config.TOKEN_KEY);
    req.user = decoded;
  } catch (err) {
    return res.status(401).send({ message: "ivalid token" });
  }
  return next();
};

module.exports = verifyToken;
Enter fullscreen mode Exit fullscreen mode

File Upload

Our blog will make use of images when posting articles. For this, we will create a middleware capable of handling the path of the images and redirecting them to a specific folder in our application: uploads/.

  • We will use the multer library. To learn more, check its documentation.

Create a file called uploads.js inside the middleware folder:

const multer = require("multer");
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "./uploads/");
  },
  filename: function (req, file, cb) {
    cb(null, new Date().toISOString() + file.originalname);
  },
});

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 1024 * 1024 * 10,
  },
});

module.exports = upload;
Enter fullscreen mode Exit fullscreen mode

Utilities

Inside the utils/ folder, we will write the truncanteText function. Its use is simple. The function takes a text and returns a small part of it. It will be used to send a small summary of the article content as a query from our API.

Create a file called truncanteText.js inside the utils folder with the following content:

const trucanteText = (content, limit) => {
  const filteredContent = content.replace(/#{1,3}\s|```
{% endraw %}
/g, "");
  const words = filteredContent.split(" ");
  if (words.length > limit) {
    return words.slice(0, limit).join(" ") + "...";
  }
  return content;
};

module.exports = trucanteText;
{% raw %}

Enter fullscreen mode Exit fullscreen mode

Routes

User

To write our user routes, we will use the following libraries:

  • express - to create our routes.
  • jsonwebtoken - to create our authentication and signing key.
  • connection.js - our connection module to perform queries to the database.

In routes/user.js:


js
require("dotenv").config();
const express = require("express");
const dbConnection = require("../connection");
const jwt = require("jsonwebtoken");

const router = express.Router();


Enter fullscreen mode Exit fullscreen mode

Next, we will write three user routes:

1 - /Signup - Route for the method of registering new users in our database.

  1. Define a POST HTTP route for /signup.

javascript
router.post("/signup", (req, res) => {
...
}


Enter fullscreen mode Exit fullscreen mode
  1. We obtain the new user's data, "name", "email", and "password", through their request.

javascript  
const user = req.body


Enter fullscreen mode Exit fullscreen mode
  1. Writing our sql query,


const query = "select email, name, password from user where email=?";


Enter fullscreen mode Exit fullscreen mode
  1. Database query, final result of the code:

javascript
router.post("/signup", (req, res) => {
  const user = req.body;
  const query = "select email, name, password from user where email=?";
  dbConnection.query(query, [user.email], (err, result) => {
    if (!err) {
      if (result.length <= 0) {
        const query = "insert into user (name,email,password) values(?,?,?)";
        dbConnection.query(
          query,
          [user.name, user.email, user.password],
          (err, results) => {
            if (!err) {
              return res
                .status(200)
                .json({ message: "You are Successfully Registrated" });
            } else {
              return res.status(500).json(err);
            }
          },
        );
      } else {
        return res.status(400).json({ message: "Email already registrated!" });
      }
    }
    return res.status(500).json(err);
  });
});


Enter fullscreen mode Exit fullscreen mode

2. /Login - let's declare the route for user authentication in our application.

  1. Define an HTTP POST route for /Login.

javascript
router.post("/login", (req, res) => {
...
}


Enter fullscreen mode Exit fullscreen mode
  1. We obtain the user's data, "email" and "password", through their request.

javascript  
const user = req.body


Enter fullscreen mode Exit fullscreen mode
  1. Writing our sql query,

javascript
const query = "select id, name, email, password from user where email=?";


Enter fullscreen mode Exit fullscreen mode
  1. Querying the database, handling the query result, and generating the token:

javascript
router.post("/login", (req, res) => {
  const user = req.body;
  const query = "select id, name, email, password from user where email=?";
  dbConnection.query(query, [user.email], (err, result) => {
    if (!err) {
      if (result.length <= 0 || result[0].password != user.password) {
        return res
          .status(401)
          .json({ message: "wrong password, try it again" });
      } else if (result[0].password === user.password) {
        const response = {
          userId: result[0].id,
          name: result[0].name,
          email: result[0].email,
        };
        const accessToken = jwt.sign(response, process.env.TOKEN_KEY, {
          expiresIn: process.env.TOKEN_LIFE,
        });
        const refreshToken = jwt.sign(response, process.env.TOKEN_REFRESH, {
          expiresIn: process.env.TOKEN_LIFE_REFRESH,
        });

        res.status(200).json({
          token: accessToken,
          expiresIn: process.env.TOKEN_LIFE,
          tokenRefresh: refreshToken,
          name: result[0].name,
          email: result[0].email,
          id: result[0].id,
        });
      }
    } else {
      return res.status(500).json({ message: "something went wrong", err });
    }
  });
});


Enter fullscreen mode Exit fullscreen mode

If the credentials are correct, two JWT tokens are generated: one for regular access (accessToken) and another for refresh (refreshToken). The tokens contain information about the user, such as id, name, and email. The tokens are signed using secret keys defined in the environment variables.

3. /Refresh-Token - Route for re-authentication of our user in the application.

  1. Define an HTTP POST route for "/refresh-token".

javascript
router.post("/refresh-token",  (req, res) => {...}


Enter fullscreen mode Exit fullscreen mode
  1. We obtain the user's previous refresh token through the request:

javascript
  const refreshToken = req.body.refreshToken;


Enter fullscreen mode Exit fullscreen mode
  1. We verify our refresh token:

javascript
if (!refreshToken)
  return res.status(401).json({ message: "Refresh token missing" });


Enter fullscreen mode Exit fullscreen mode
  1. We renew the access tokens:

javascript
const token = jwt.sign(response, process.env.TOKEN_KEY, {
  expiresIn: process.env.TOKEN_LIFE,
});

 const refreshToken = jwt.sign(response, process.env.TOKEN_REFRESH, {
    expiresIn: process.env.TOKEN_LIFE_REFRESH,
  });


Enter fullscreen mode Exit fullscreen mode
  1. Here is the final result:

javascript
router.post("/refresh-token", (req, res) => {
  const refreshToken = req.body.refreshToken;
  if (!refreshToken)
    return res.status(401).json({ message: "Refresh token missing" });

  jwt.verify(refreshToken, process.env.TOKEN_REFRESH, (err, decoded) => {
    if (!err) {
      const response = {
        userId: decoded.id,
        name: decoded.name,
        email: decoded.email,
      };
      const token = jwt.sign(response, process.env.TOKEN_KEY, {
        expiresIn: process.env.TOKEN_LIFE,
      });
      const refreshToken = jwt.sign(response, process.env.TOKEN_REFRESH, {
        expiresIn: process.env.TOKEN_LIFE_REFRESH,
      });

      res.status(200).json({
        id: decoded.id,
        name: decoded.name,
        email: decoded.email,
        token: token,
        expiresIn: process.env.TOKEN_LIFE,
        tokenRefresh: refreshToken,
      });
    } else {
      res.status(500).json(err);
    }
  });
})


Enter fullscreen mode Exit fullscreen mode

Articles

In routes/article.js, we will start by calling the following libraries:

  • express - to create our routes.
  • jsonwebtoken - to create our authentication and its signing key.
  • connection.js - our connection module to perform the database queries.
  • authentication.js - our authentication middleware to protect private routes.
  • upload.js - our middleware for handling image uploads.

javascript
const express = require("express");
const dbConnection = require("../connection");
const fs = require("fs");
const path = require("path");
//middleware
const upload = require("../middleware/upload");
const authentication = require("../middleware/auth");
// utils
const trucanteText = require("../utils/trucanteText");

const router = express.Router();
//routes...
module.exports = router;


Enter fullscreen mode Exit fullscreen mode

Then we will write the routes:

1. /add - Route to add a new article.

  1. We define a POST HTTP route for "/add", passing the authentication middleware and upload.single("thumb") for uploading a single image:

javascript
router.post("/add", upload.single("thumb"), authentication, (req, res, next) => {
...
}


Enter fullscreen mode Exit fullscreen mode
  1. We collect the request body data (req.body) related to the new article, such as userId, title, and content. Additionally, we obtain the path of the uploaded image using the multer middleware and the name of the specified field ("thumb").


...
const { userId, title, content } = req.body;
const image = req.file.path;


Enter fullscreen mode Exit fullscreen mode
  1. Write the SQL query to insert the new article data into the articles table in the database. The values to be inserted are obtained from the variables userId, title, image, and content.

javascript
 const query ="insert into articles (userId, title, image, content) values(?,?,?,?)";


Enter fullscreen mode Exit fullscreen mode
  1. The final result of our code:

javascript
router.post(
  "/add",
  upload.single("thumb"),
  authentication,
  (req, res, next) => {
    const { userId, title, content } = req.body;

    const image = req.file.path;

    const query =
      "insert into articles (userId, title, image, content) values(?,?,?,?)";

    dbConnection.query(
      query,
      [userId, title, image, content],
      (err, result) => {
        if (!err) {
          return res.status(200).json({ message: "article has been added" });
        } else {
          return res.status(500).json(err);
        }
      },
    );
  },
);


Enter fullscreen mode Exit fullscreen mode

2. /delete/:id - For removing articles.

  1. Define an HTTP DELETE route for "/delete/:id". This route will be used to remove an article from the database. Authentication is verified before allowing the execution of this route.

javascript
router.delete("/delete/:id", authentication, (req, res) => {
...
}


Enter fullscreen mode Exit fullscreen mode
  1. Get the id parameter from the URL using req.params. This id will be used to identify the article that will be removed from the database.

javascript
const id = req.params.id;


Enter fullscreen mode Exit fullscreen mode
  1. Write an SQL query to delete an article from the "articles" table in the database based on the provided id.

javascript
const query = "delete from articles where id=?";


Enter fullscreen mode Exit fullscreen mode
  1. Here is the final result of our code:

javascript
router.delete("/delete/:id", authentication, (req, res) => {
  const id = req.params.id;
  const query = "delete from articles where id=?";
  dbConnection.query(query, [id], (err, result) => {
    if (!err) {
      if (result.affectedRows == 0) {
        return res.status(404).json({ message: "article id does not found" });
      } else {
        return res.status(200).json({ message: "article deleted" });
      }
    } else {
      return res.status(500).json(err);
    }
  });
});


Enter fullscreen mode Exit fullscreen mode

3. /edit/:id - Route to Edit Articles.

  1. Define an HTTP PATCH route for "/edit/:id". This route will be used to update an article in the database. Authentication is verified before allowing the execution of this route.

javascript
router.patch("/edit/:id", authentication, (req, res) => {...}


Enter fullscreen mode Exit fullscreen mode
  1. We collect the id parameter from the URL and the article data from the request body (req.body). The id is used to identify the article that will be updated.

javascript
const id = req.params.id;
const article = req.body;


Enter fullscreen mode Exit fullscreen mode
  1. Write the SQL query to update an article in the "articles" table of the database based on the provided id.

javascript
const query = "update articles set title=?, content=? where id=?";


Enter fullscreen mode Exit fullscreen mode
  1. Here is the final result of our code:

javascript
router.patch("/edit/:id", authentication, (req, res) => {
  const id = req.params.id;
  const article = req.body;
  const query = "update articles set title=?, content=? where id=?";
  dbConnection.query(
    query,
    [article.title, article.content, id],
    (err, result) => {
      if (!err) {
        if (result.affectedRows == 0) {
          return res.status(404).json({ message: "article id does not found" });
        } else {
          return res
            .status(200)
            .json({ message: "article updated succesfullt" });
        }
      } else {
        return res.status(500).send({ message: "something went wrong" }, err);
      }
    },
  );
});


Enter fullscreen mode Exit fullscreen mode

4. /getAll - Route for querying all Articles

  1. Define a HTTP GET route for "/getAll". This route will be used to retrieve all the articles registered in the database.

javascript
router.get("/getAll", (req, res) => {}


Enter fullscreen mode Exit fullscreen mode
  1. Write an SQL query to select all fields from all articles in the "articles" table in the database.

javascript
const query = "SELECT * FROM articles";


Enter fullscreen mode Exit fullscreen mode
  1. Handle the query and the final result:

javascript
router.get("/getAll", (req, res) => {
  const query = "SELECT * FROM articles";
  dbConnection.query(query, (err, results) => {
    if (err) {
      res.status(500).send({ message: "could not get articles" });
    } else {
      const processedResults = results.map((article) => {
        const { content, ...rest } = article;
        const resumedContent = trucanteText(content, 5);
        return { content: resumedContent, ...rest };
      });
      res.json(processedResults);
    }
  });
});


Enter fullscreen mode Exit fullscreen mode

5. /getById/:id - Route to get an Article by ID.

  1. Define a HTTP GET route for "/getById/:id".

js
router.get("/getById/:id", (req, res) => {...}


Enter fullscreen mode Exit fullscreen mode
  1. Collect the id parameter from the URL using req.params.

js
const id = req.params.id;v


Enter fullscreen mode Exit fullscreen mode
  1. Write an SQL query to select all fields of the article from the "articles" table in the database where the id matches the id provided in the URL.

javascript
const query = "SELECT * FROM articles WHERE id=?";


Enter fullscreen mode Exit fullscreen mode
  1. Here is the final result of our code:

javascript
router.get("/getById/:id", (req, res) => {
  const id = req.params.id;
  const query = "SELECT * FROM articles WHERE id=?";
  dbConnection.query(query, [id], (err, results) => {
    if (err) {
      res.status(500).send({ message: "something whent wrong" }, err);
    } else if (results.length === 0) {
      res.status(404).send({ message: "article not found" });
    } else {
      res.json(results[0]);
    }
  });
});


Enter fullscreen mode Exit fullscreen mode

Conclusion

First of all, I would like to thank you for dedicating your time to this tutorial, and congratulations on completing it! I hope it was helpful and easy to follow.

Feel free to explore more of your skills with this application. Some routes were intentionally left out so that you feel encouraged to write them yourself. If you're not sure where to start, here are a few suggestions:

  • Route for editing user data
  • Route for password recovery via email
  • Route for updating user password
  • Route for downloading a specific article in markdown format

This tutorial also covers the frontend of our application. After all, what's the point of having an API if you're not going to use it somewhere, right? So, move on to the next step, click here to continue our tutorial.

See you next time! o/

Top comments (0)