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:
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
…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
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>
Once inside your application, create the database:
mysql> create database blog_db;
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;
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)
);
- 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)
);
- 3 RefreshToken
create table refreshToken(
id int primary key AUTO_INCREMENT,
expiresIn int,
userName varchar(250),
userId int NOT NULL
);
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)
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');
First Steps
Starting the Application
Let's start by creating the package.json
. In your terminal, run the following command:
npm init
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)
Installing dependencies
Next, let's install the libraries.
npm i express multer mysql bcryptjs cors dotenv jsonwebtoken
Criando o servidor
Dentro da raiz do projeto, crie o arquivo server.js
e adicione o seguinte código:
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} `);
}
});
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
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;
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
You can use the terminal itself to generate your token
. First, enter node.
$ node
Next, using the crypto
library:
> require("crypto").randomBytes(64).toString('hex')
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;
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;
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 %}
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();
Next, we will write three user routes:
1 - /Signup - Route for the method of registering new users in our database.
- Define a POST HTTP route for
/signup
.
javascript
router.post("/signup", (req, res) => {
...
}
- We obtain the new user's data, "name", "email", and "password", through their request.
javascript
const user = req.body
- Writing our sql query,
const query = "select email, name, password from user where email=?";
- 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);
});
});
2. /Login - let's declare the route for user authentication in our application.
- Define an HTTP POST route for
/Login
.
javascript
router.post("/login", (req, res) => {
...
}
- We obtain the user's data, "email" and "password", through their request.
javascript
const user = req.body
- Writing our sql query,
javascript
const query = "select id, name, email, password from user where email=?";
- 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 });
}
});
});
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.
- Define an HTTP POST route for "/refresh-token".
javascript
router.post("/refresh-token", (req, res) => {...}
- We obtain the user's previous refresh token through the
request
:
javascript
const refreshToken = req.body.refreshToken;
- We verify our refresh token:
javascript
if (!refreshToken)
return res.status(401).json({ message: "Refresh token missing" });
- 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,
});
- 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);
}
});
})
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;
Then we will write the routes:
1. /add
- Route to add a new article.
- We define a POST HTTP route for "/add", passing the
authentication
middleware andupload.single("thumb")
for uploading a single image:
javascript
router.post("/add", upload.single("thumb"), authentication, (req, res, next) => {
...
}
- We collect the request body data (
req.body
) related to the new article, such asuserId
,title
, andcontent
. 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;
- 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 variablesuserId
,title
,image
, andcontent
.
javascript
const query ="insert into articles (userId, title, image, content) values(?,?,?,?)";
- 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);
}
},
);
},
);
2. /delete/:id
- For removing articles.
- 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) => {
...
}
- 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;
- 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=?";
- 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);
}
});
});
3. /edit/:id
- Route to Edit Articles.
- 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) => {...}
- We collect the
id
parameter from the URL and the article data from the request body(req.body)
. Theid
is used to identify the article that will be updated.
javascript
const id = req.params.id;
const article = req.body;
- 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=?";
- 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);
}
},
);
});
4. /getAll
- Route for querying all Articles
- 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) => {}
- Write an SQL query to select all fields from all articles in the "articles" table in the database.
javascript
const query = "SELECT * FROM articles";
- 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);
}
});
});
5. /getById/:id
- Route to get an Article by ID.
- Define a HTTP GET route for "/getById/:id".
js
router.get("/getById/:id", (req, res) => {...}
- Collect the
id
parameter from the URL usingreq.params
.
js
const id = req.params.id;v
- 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=?";
- 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]);
}
});
});
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)