How to create a Blog API using Node.js and Express.js Framework
Tags: webdev, node, javascript, tutorial
Published: 2023-01-10 20:49:52

## Introduction
Hello there,

In this tutorial, you are going to learn how to build a Blog API with Node.js and Express.js Framework. Anyone should be able to view and read **published** articles but only logged-in users can perform tasks like creating, editing and deleting an article.

**Major Functionalities:**
- Users can register and login.
- Users can view a list of published articles.
- Users can read a published article.
- Only logged-in users can create an article.
- A Logged-in user can publish their own article.
- A logged-in user can edit their own article.
- A logged-in user can delete their own article.
- A logged-in user can view a list of their own articles.


## Prerequisites
I assumed that you are already familiar with JavaScript, Node.js & Express.js, MongoDB & Mongoose and how a Web Server works.

**You'll also need**
- [VS Code](https://code.visualstudio.com/download)
- [Node.js](https://nodejs.org/en/download/)
- MongoDB Server - [MongoDB Atlas](https://www.mongodb.com/cloud/atlas/efficiency?utm_content=rlsapostreg&utm_source=google&utm_campaign=search_gs_pl_evergreen_atlas_general_retarget-brand-postreg_gic-null_emea-all_ps-all_desktop_eng_lead&utm_term=&utm_medium=cpc_paid_search&utm_ad=&utm_ad_campaign_id=14412646473&adgroup=131761130372&gclid=CjwKCAiA2fmdBhBpEiwA4CcHzSKdsbC5TcKgUOB1NipmwMc2PXhZOWs5p7FnQ7Np_dNsH9VcX0jdhxoC9e8QAvD_BwE) or [MongoDB Compass](https://www.mongodb.com/products/compass)
- API Testing Platform - [Thunder Client](https://www.thunderclient.com/) or [Postman](https://www.postman.com/)


## Environment Setup

**_Project Initialization_**
We start by creating a directory that will contain the project files. Open your Terminal app (Linux/Mac) or Command Line on Windows. If you're on Windows I'll recommend that you use Git Bash instead of the default command line.

I'm going to name the project as `blog_api` but you can give it any name you want. In your terminal, enter the following command to create the project folder, `mkdir blog_api`.

Navigate to the project folder you just created with the following command, `cd blog_api`.

Now that we're in the project folder which I'll subsequently refer to as **root directory**, let's initialize a Node.js project by executing this command, `npm init-y`. A `package.json` file is created in the root directory.

Still, in the root directory create the following files `.env` and **.gitignore**.

Later in `.env` we're going to store sensitive information about our API. These include any information we do not want to make public such as DATABASE URL, JWT SECRET and so on. While `.gitignore` would contain names of files that should not be exposed to the public, for example, your `.env` file.

**_Install Dependencies_**
Dependencies are programs usually referred to as modules libraries or packages that perform a specific task and can be installed and used in our programs. By using the appropriate dependencies we would not have to code everything from scratch, and so the development duration is shortened considerably.

Development Dependencies (Dev dependencies) are a category of dependencies that are used only during development.

Here is a list of dependencies that we'll use in this project:

`express` - For creating our web server and handing routes.

`mongoose` - An Object Document Mapper (ODM, that helps to simplify working with MongoDB.

`bcrypt` - Provides APIs for encrypting and decrypting passwords. We don't want to save user passwords as plain text in the database as this could lead to serious security issues.

`passport` - Authentication library which provides a variety of methods known as strategies for implementing authentication such as local, jwt, and even supports authentication via a [Social Identity Provider (IdP)](https://docs.dremio.com/cloud/security/authentication/idp/social-idp/) like Google and GitHub.

`jsonwebtoken` - We'll use it to generate and verify JSON Web Token (JWT) that will be used for authenticating users.

`passport-jwt` - Enable us to implement a passport `jwtStrategy`.

`passport-local` - Enable us to implement a passport `local Strategy`.

`dotenv` - A library for getting environment variables from a `.env` file.

`nodemon` - Auto refreshes the server so that we don't have to manually restart it whenever there is a change in any file. We're going to install `nodemon` as a Dev Dependency.

Enter the command below to install normal dependencies in one go:

`npm install express mongoose bycrpt passport jsonwebtoken passport-jwt passport-local dotenv`

Next, install nodemon as a devDependency like this, `npm install nodemon`

_As an aside, since you are likely going to be using `nodemon` in other projects it is a common practice to install it globally as done below._

`npm install nodemon -g`

After setting up our project environment and installing the all dependencies we are ready to start writing the codes for our Blog API.

The first thing we are going to do is to create a server using `express`.

## Create Server
Create a new file `app.js` in the root directory. The file app.js would serve as the entry point to our API, it is the file that gets executed when we start the server.

Let's proceed by writing the following code in the app.js file.

```javascript
//app.js

const express = require('express');

//Create server
const app = express();

app.use(express.json()); //Parse payload
app.use(express.urlencoded({extended: false}));

//Home Route
app.get('/', (req, res) => {
    return res.status(200).send("Hello, welcome to Blog API. This API allow users to create or read articles created by others. Please go to /api/articles to get a list of published articles or checkout the README.md file in the GitHub Repository to learn more about how it works and how to run or test it. Thank you!");
});

//Error Handler
app.use((error, req, res, next) => {
    if (error) {
        return res.status(500).json({ message: "Internal Server Error." });
    } 
    next(); 
});

//Catch-all route
app.get('*', (req, res) => {
    return res.status(404).json({ message: "Not Found"});
});

module.exports = app;
```

We imported `express` and created an `express` object. The express object is stored in a variable `app`. Next, we added the following middleware `express.json` and `express.urlencoded` to extract and properly format data contained in the request body. Then we created the home route `/`. This will be the default route of our API.

Next, we added an [error handler](https://expressjs.com/en/guide/error-handling.html) middleware and a catch-all route. A [catch-all](https://social.msdn.microsoft.com/Forums/en-US/069aa16c-b139-4dda-9fd4-1460c49d11bb/what-is-catch-all-route-in-mvc?forum=aspmvc) route is used to prevent errors in cases where a request has a route that is not defined in the API.

Finally, we exported the `app` variable.


## Database Connection
In the root directory create a new folder `src`. In `src` create a file `db.js`. The file `db.js` is going to contain codes for connecting to the database.

```javascript
/* /src/db.js */

const mongoose = require('mongoose');
require('dotenv').config();

class Database {
    constructor() {
        this._connect();
    }
    _connect() {
        mongoose.connect(process.env.DATABASE_URI, {useNewUrlParser: true, useUnifiedTopology: true})
        .then(()=> {
            console.log('Connected to database successfully.');
        })
        .catch(() => {
            console.log('Database connection failure.');
        })
    }
}

module.exports = new Database();
```

We imported `mongoose` for connecting to the database and `dotenv` to get the `DATABASE_URL` from `.env` file.

The major feature of this file is a class `Database` which contains a method `_connect` that runs when an instance of the `Database` is created. Within `_connect` call we `mongoose.connect` and passed in `DATABASE_URL`. The result is a promise and like any promise, it has a `.then` method that runs if there are no errors and a `.catch` method that runs otherwise.

Finally, we create an instance of `Database `and exported it.


## Models
Previously, we installed `mongoose` as a **Dev Dependency** and I explained that mongoose is an ODM for MongoDB. We are going to use `mongoose` to create our database schemas. You can imagine a schema as a blueprint that can be used create a certain type of record. A model in this context is an instance of a **record** created using the schema that we defined.

We going to create two schemas, `userSchema`, and `articleSchema`.

In src create a new folder, `models`. The create two new files, user.model.js and article.model.js in `models`.

**_User Schema_**

```javascript
/* src/models/user.model.js */

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');


const Schema = mongoose.Schema;

const userSchema = new Schema({
        //Mandatory fields
        firstName: {
            type: String,
            required: true
        },
        lastName: {
            type: String,
            required: true
        },
        email: {
            type: String,
            lowercase: true,
            required: true,
            unique: true
        },
        password: {
            type: String,
            required: true,
        },
        createdAt: {
            type: Date,
            default: Date.now()
        }
    }
);

//Hash password before saving to database.
userSchema.pre('save', async function(next) {
    const hash = await bcrypt.hash(this.password, 10);
    this.password = hash;
    next();
});

//Validate that user password stored in the DB matches that which is provided during signin process.
userSchema.methods.validatePassword = async function(password) {
    const result = await bcrypt.compare(password, this.password);
    return result;
}

module.exports = mongoose.model("users", userSchema);
```
We imported `mongoose` and `bcrypt`. Next, we created a `Schema` object with `mongoose`. Then we defined the fields for a **user** record as an object and the object into `mongoose.Schema`.

The next part is a [pre-save hook](https://medium.com/@justinmanalad/pre-save-hooks-in-mongoose-js-cf1c0959dba2), in which we called `bcrypt.hash`, a function that can convert a plain text password to a hashed or encrypted string before saving a **user** record to the database.

We also defined an [instance method](https://www.geeksforgeeks.org/mongoose-schemas-instance-methods/) `userSchema.methods.validatePassword`,  which calls `bcrypt.compare`, a function that checks if a given plain text password matches  the hashed password in a **user** record coming from the database.

Finally, we created a "user model", `mongoose.model("users", userSchema)` and exported it.

**_<u>Article Schema</u>_**

```javascript
/* src/models/src/article.model.js */

const { Schema, model } = require('mongoose');


const articleSchema = new Schema(
    {
        title: {
            type: String,
            required: true
        },
        description: String,
        author: String,
        state: {
            type: String, 
            enum: ["draft", "published"],
            default: "draft",
        },
        body: {
            type: String,
            required: true
        },
        readCount: {
            type: Number,
            default: 0
        },
        readingTime: Number,
        tags: {
            type: Array,
            lowercase: true
        },
        timestamp: {
            type: Date,
            default: Date.now()
        }
    }
);

//Calculate article readingTime
articleSchema.methods.calculateReadingTime = function(content) {
    const wordCount = this.body.split(' ').length;
    const readingTime = wordCount / 250; //250 words per minute is average reading speed
    const readingTimeMinutes = Math.ceil(readingTime);
    return readingTimeMinutes;
};

module.exports = model("articles", articleSchema);
```
We started by importing `Schema` and `model` from `mongoose` by de-structuring. Next, we created `aticlesSchema` with `Schema`. Then we defined the fields for an **article** record as an object and we passed the object to `Schema`.

We also defined an instance method, `articleSchema.methods.calculateReadingTime`, which can calculate the reading time of an article based on the length of the article's content.

Lastly, we created an **article model**, model("articles", articleSchema) and exported it.

## Routes
An HTTP request typically embeds certain information in its headers. One of those information is the intended destination of the request in the server. Routing can be defined as the process of determining the intended destination of a request sent to a server.

Express comes with a `Router` method that makes it easy to create routes in our server and APIs.

In this project, we'll define two routes `/auth` and `/articles`. As the names suggest `/auth` will handle any request that pertains to user authentication while `/articles` will handle any request that is about the management of articles.

Before going ahead to create our routes, we need to create a set of **authentication middleware**. In the routes that we'll create later, specific authentication middleware will perform a specific actions.

**_Authentication Middleware_**

A middleware is a function that gets executed between a request and a response. For this project, we are going to create three authentication middlewares. The first middleware is responsible for extracting JWT tokens from the request headers. The JWT token will be used to authenticate users when they try to access certain protected routes. The remaining two middlewares handle user registration and login respectively.

In the src folder create a new folder and call it `middlewares`. In middlewares, create a new file and call it auth.middleware.js. Write the following code in the **auth.middleware.js** file:

```javascript
/* src/middlewares/auth.middleware */

const passport = require('passport');
const jwtStrategy = require('passport-jwt').Strategy;
const ExtractJWT = require('passport-jwt').ExtractJwt;
const localStrategy = require('passport-local');
const UserModel = require('../models/user.model');
require('dotenv').config();

//Retrieve and varify JWT token
passport.use(
    new jwtStrategy(
        {
            secretOrKey: process.env.JWT_SECRET,
            jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
        },

        async(token, done) => {
            try {
                const user = await token.user;
                return done(null, user);
            } catch(error) {
                done(error);
            }
        }
    )
);

//Passport local strategy signup
passport.use('signup',
    new localStrategy(
        {
            usernameField: 'email',
            passwordField: 'password',
            passReqToCallback: true
        },
        async(req, email, password, done) => {
            try {
                //First, check whether user with the email provided already exists.
                const oldUser = await UserModel.findOne({ email });
                if (oldUser) {
                    return done(null, false, { message: "A user with this email already exists."});
                } else {
                    //If the email does not exist, signup user.
                    const body = req.body;
                    let user = await UserModel.create({ firstName: body.firstName, lastName: body.lastName, email, password });
                    user = user.toObject();
                    delete user.password;
                    return done(null, user, { message: "Signup was successful."});
                }
            } catch(error) {
                done(error);
            }
        }
    )
);

//Passport local strategy signin
passport.use('signin', 
    new localStrategy(
        {
            usernameField: 'email',
            passwordField: 'password'
        },
        async(email, password, done) => {
            try {
                const user = await UserModel.findOne({ email });
                if (!user) {
                    return done(null, false, { message: "User not found." });
                }
                
                const isValidPassword = await user.validatePassword(password);
                if (!isValidPassword) {
                    return done(null, false, {message: "Password is incorrect." });
                }
                return done(null, user, { message: `Welcome back ${user.firstName}.`});             
            } catch(error) {
                done(error);
            }
        }
    )
);
```

We imported `passport`, `passport-jwt` to create a jwt strategy, `passport-jwt` again to extract JWT tokens from the headers, **passport-local** to create a local strategy, `user.model` and `dotenv`.

<u>Retrieve and Verify Token</u>
This middleware implements a `jwtStrategy` with passport. The `jwtStrategy` strategy takes two arguments; an **object** and a **callback**. 

In the **object**, we defined the following properties; `secretOrKey` which is set to the `JWT_SECRET` that is stored in `.env` file, `jwtFromRequest` which specifies where to get the JWT token from.

The **callback** takes two parameters, `token` and `done`. Here, `token` represents the decrypted **JWT** token extracted from the request headers while `done` is a function which operates similarly as `next` function. When the **callback** is executed `user` object is extracted from `token` and passed into `done` as a second argument after `null`. Any error is caught and passed into `done`.

<u>Signup Middleware</u>
The middleware `"signup"`, uses passport `localStrategy` to add a new user to the database. We are going to add it to `/signin` route later. We passed two arguments, an **object** and a **callback** into `localStrategy`.

The **object** argument has the following properties; `usernameField` equal to `"email"`, `passwordField` equal to `"password"` and `passReqToCallback` equal to true. By default, only `usernameField` and `passwordField` fields are passed to the callback but the inclusion of `passReqToCallback` will ensure that the `req` object is passed as well so that other fields are also accessible inside the **callback**.

The **callback** takes four parameters; `req`, `email`, `password` and `done`. If a user with same email already exists in the database, send back a message explaining that the given email has already been registered. Otherwise, get `user data` from `req`. Then create a new user in the database by passing `user data` to `userModel.create`. Finally, return _user data_ as an object.

<u>Signin Middleware</u>
The "signin" middleware also uses passport `localStrategy`. However, it does not create a new user but rather helps in checking the validity of a user's credentials when they want to sign-in. When we create `/singin` route later we're going to add `"signin"` as a middleware. 

The `localStrategy` object takes two arguments; an **object** and a **callback**.

The **object** argument has `usernameField` equal to "email" and `"passwordField"` equal to `"password"`. 

We passed in three parameter, `email`, `password` and `done` to the **callback**.  We start by searching the database for the user whose **email** matches what was provided in the request. If a match was found based on email we then proceed to compare the password property of the match and what was provided in the request. 

We defined `validatePassword` as an [instance method](https://javascript.plainenglish.io/create-own-method-in-mongoose-model-5fefa7975b9d) in the user schema. If password comparison is successful call done and pass in **null**, the matched **user** and a **message**. Otherwise, call done and pass in **null**, **false** and a **message**. 

After we finish writing the code for the various authentication middleware, we are set to start creating the **routes**.

In the **src** folder create a new folder and call it **routes**. In routes create the following files **auth.route.js** and **articles.route.js**. 

Starting with **/auth** route, we are going create two endpoints; "/auth/signup" and "/auth/signin". Open `auth.route.js` file.

_Import modules_

```javascript
const authRoute = require('express').Router();
const jwt = require('jsonwebtoken');
const passport = require('passport');
require('dotenv').config();
```
We imported the following; `express` so that we can create a Router with the `Router` method, `jsonwebtoken` for creating JWT token, `passport` and `dotenv`.


_**/signup**_

```javascript
//Signup route
authRoute.post('/signup', async(req, res) => {
    passport.authenticate('signup', {session: false}, async(err, user, info) => {
        if (!user) {
            return res.status(400).json(info)
        } else return res.status(201).json({info, user });
    })(req, res)
});
```

This route has a POST method. The [request handler ](https://cloud.yandex.com/en/docs/functions/lang/nodejs/handler)function calls `passport.authenticate` which takes three arguments. The first argument is "signup" (references signup middleware that was defined in `auth.middleware.js`). The second is an option that prevents the signup process from saving a session, and lastly a **callback**.

When a request comes to **/signup** route the "signup" middleware is executed, it returns three values **err**, **user** and **info** which are accessible inside the **callback**. 

The next steps are completed inside the callback. If **user** is invalid return **info** with a 400 status code. Otherwise, return **user** and **info** with a 201 status code.

_**/signin**_

```javascript
//Signin route
authRoute.post('/signin', async(req, res, next) => {
    passport.authenticate('signin', async(err, user, info) => {
        try {
            if (!user) {
                return res.status(400).json(info);
            }
            req.login(
                user,
                {session: false},
                async(error) => {
                    if (error) {
                        next(error);
                    }
                    const body = { _id: user._id, email: user.email };
                    const token = jwt.sign({ user: body }, process.env.JWT_SECRET, { expiresIn: '1hr'});
                    return res.json({ info, token });
                }
            )
        } catch(error) {
            next(error);
        }
    })(req, res, next);
})
```
Just like "/signup", the "/signin" route has a POST method. The request handler calls `passport.authenticate` with two arguments, namely "signin" (references "signup" middleware) and a **callback**.

When a request comes to "/signin" route, "signin" middleware is executed and the following values are returned; **err**, **user** and **info** which are then accessible inside the callback.

Now, let's examine the **callback** in more details. First, check whether **user** is invalid and return **info** with a status of 400 if so. If user is valid call `req.login` with the arguments as shown in the example above. The **login** method is automatically added to the **req** object by passport.

One of the arguments taken by login is another **callback**. Inside that callback is where we're going to create a JWT token. The JWT token is created by extracting certain fields from the user object. Usually any number of fields can be extracted but sensitive ones like the password field should be excluded. 

In this example, I'm going to use the id and email fields. We need to create a **user object** with those fields. The **user object** can be used to **create** a JWT token. This is achieved by passing **user object** as an argument to `jwt.sign` along with our JWT_SECRET and a third argument that sets the expiration period of the token. I've set the token to expire in 1hr but you can use a different value. Lastly, we return an object that contains the signed JWT token.

Finally, export the router.

The content of /src/routes/auth.route.js should now look similar to the code below:

```javascript
/* src/routes/auth.route.js */

const authRoute = require('express').Router();
const jwt = require('jsonwebtoken');
const passport = require('passport');
require('dotenv').config();

//Signup route
authRoute.post('/signup', async(req, res) => {
    passport.authenticate('signup', {session: false}, async(err, user, info) => {
        if (!user) {
            return res.status(400).json(info)
        } else return res.status(201).json({info, user });
    })(req, res)
});

//Signin route
authRoute.post('/signin', async(req, res, next) => {
    passport.authenticate('signin', async(err, user, info) => {
        try {
            if (!user) {
                return res.status(400).json(info);
            }
            req.login(
                user,
                {session: false},
                async(error) => {
                    if (error) {
                        next(error);
                    }
                    const body = { _id: user._id, email: user.email };
                    const token = jwt.sign({ user: body }, process.env.JWT_SECRET, { expiresIn: '1hr'});
                    return res.json({ info, token });
                }
            )
        } catch(error) {
            next(error);
        }
    })(req, res, next);
})

module.exports = authRoute;
```

## Articles Routes
Create a new file `articles.route.js` inside the `routes` folder. We are going to create our /articles routes in this folder. Some of the endpoints for `/articles` routes would be protected and accessible to only signed-in users while others would be accessible to anyone.


```javascript
/* src/routes/articles.route.js */

const router = require('express').Router();
const articlesController = require('../controllers/articles.controller');
const passport = require('passport');


router.get('/', async(req, res, next) => {
    try {
        await articlesController.getPublishedArticles(req, res);
    } catch(error) {
        next(error);
    }
});

router.get('/read/:id', async(req, res, next) => {
    try {
        await articlesController.getPublishedArticleById(req, res);
    } catch(error) {
        next(error);
    }
})

//PROTECTED ROUTES (User must be authenticated)
router.use(passport.authenticate('jwt', { session: false }));


//Get list of own articles (draft and published)
router.get('/my-articles', async(req, res, next) => {
    try {
        await articlesController.getOwnArticles(req, res);
    } catch(error) {
        next(error);
    }
})

//Create an article
router.post('/create', async(req, res, next) => {
    try {
        await articlesController.createArticle(req, res);
    } catch(error) {
        next(error);
    }
});

//Update own article state
router.patch('/publish/:id', async(req, res, next) => {
    try {
        await articlesController.publishOwnArticle(req, res)
    } catch(error) {
        next(error);
    }
})

//Edit own article
router.patch('/edit/:id', async(req, res, next) => {
    try {
        await articlesController.editOwnArticle(req, res);
    } catch(error) {
        next(error);
    }
});
```

We imported the following; `express` and called the `Router` method to create a Router, `articlesController` and `passport` for authenticating users when they try to access a protected endpoint.

**A controller is basically a file where we can define the request handlers for our routes**. 

Putting the handlers in a separate file helps us to better organize our routes.

The following endpoints are defined for the /articles route.

_Unprotected Endpoints_
 `/` route: It has a `GET` method, calls `getPublishedArticles` controller. 

`/read/:id` - It has `GET` method, calls `getPublishedArticleById` controller.

_Protected Endpoints_
Preceding these set of endpoints is a middleware `passport.authenticate`. It verifies the JWT token and ensures that only logged in users are able to access the endpoints immediately below it.

`/my-articles` - It has a `GET` method, calls `getOwnArticles` controller.

`/create` - Has `POST` method, calls `createArticle` controller.

`/publish/:id` - Has a `PATCH` method, calls `publishOwnArticle` controller.

`/edit/:id` - Has a `PATCH` method and calls `editOwnArticle` controller.

`/delete/:id` - It has a `DELETE` method and calls `deleteOwnArticle` controller.

At the end of the file we exported our route.


## Controllers
Create a new folder `controllers` inside the `src` folder. Then create a new file `articles.controller.js` inside `controllers` folder.

```javascript
/* src/controllers/articles.controller.js */

const articlesService = require('../services/articles.service');


//Optional query parameters:
//Filtering (author, title and tags)
//Ordering (readCount, readingTime and timestamp)
const getPublishedArticles = async(req, res) => {
    let articles;
    const query = req.query;
    if (query) {
        articles = await articlesService.getPublishedArticles(query);
    } else {
        articles = await articlesService.getPublishedArticles(null);
    }    
    return res.status(200).json(articles);
}

const getPublishedArticleById = async(req, res) => {
    const id = req.params.id;
    const articleAndAuthor = await articlesService.getPublishedArticleById(id);
    return res.status(200).json(articleAndAuthor);
}

const createArticle = async(req, res) => {
    const body = req.body;
    const user = req.user.email; //Email of signed in user derived from decrypted jwt token
    const article = await articlesService.createArticle(body, user);
    return res.status(201).json({message: "New draft article created.", article });
}

const publishOwnArticle = async(req, res) => {
    const id = req.params.id;
    const user = req.user.email;
    const article = await articlesService.publishOwnArticle(id, user);
    if (article) {
        return res.status(200).json({message: "Congratulations, your article was successfully published.", article })
    }
}

const editOwnArticle = async(req, res) => {
    const id = req.params.id;
    const body = req.body;
    const user = req.user.email;
    const article = await articlesService.editOwnArticle(id, body, user);
    if (article) {
        return res.status(200).json({message: "Your changes have been saved.", article })
    } else {
        return res.status(401).json({ message: "You don't have the permission to edit this article."})
    }
}

const deleteOwnArticle = async(req, res) => {
    const id = req.params.id;
    const user = req.user.email;
    const article = await articlesService.deleteOwnArticle(id, user);
    if (article) {
        return res.status(200).json({ message: "Deleted one (1) article."})
    } else {
        return res.status(401).json({message: "You don't have the permission to delete this article."})
    }   
}

const getOwnArticles = async(req, res) => {
    const user = req.user.email;
    let state;
    let articles
    if (req.query) {
        state = req.query.state;
        articles = await articlesService.getOwnArticles(user, state);
    } else {
        articles = await articlesService.getOwnArticles(user, null);
    }
    if (articles) {
        return res.status(200).json(articles);
    } else {
        return res.status(401).json({message: "Please signin to see a list of your articles."})
    }
}

module.exports = {
    getPublishedArticles,
    getPublishedArticleById,
    createArticle,
    publishOwnArticle,
    editOwnArticle,
    deleteOwnArticle,
    getOwnArticles,
}
```
First, We imported `articlesService` which contain functions that defines the logic for manipulating the database.

_Articles Controllers_
Each controller function calls a corresponding service function.

At the end of the file we exported the controller functions.


## Services
As mentioned above, the service module is where we're going to define a group of functions that will contain all the logic for manipulating the database.

Create a new folder `services` inside the `src` folder. Then create a new file `articles.service.js` inside `services`.

_Importing modules_

```javascript
const ArticleModel = require('../models/article.model');
const UserModel = require('../models/user.model');
```
We imported the following modules; `ArticleModel` and `UserModel`.

_Get published Articles_

```javascript
const getPublishedArticles = async(query) => {
    let articles;
    if (query) {
        //
        const searchBy = {};
        const orderBy = {};
        searchBy.state = "published" //Ensure that only published articles are returned
        
        //Filter parameters (author, title and tags)
        if (query.author) {
            searchBy.author = query.author;
        }
        if (query.title) {
            searchBy.title = query.title;
        }
        if (query.tags) {
            let tags = query.tags.split(',');
            //Ensure that all the tags are in lowercase to match with structure of saved documents
            tags = tags.map((tag) => {
                return tag.toLowerCase();
            });
            searchBy.tags = {$in: tags};
        }

        //Order parameters (readCount, readingTime and timestamp)
        if (query.readCount) {
            if (query.readCount == 'asc') {
                orderBy.readCount = 1;
            } else if (query.readCount == 'desc') {
                orderBy.readCount = -1;
            }
        }
        if (query.readingTime) {
            if (query.readingTime == 'asc') {
                orderBy.readingTime = 1;
            } else if (query.readingTime == 'desc') {
                orderBy.readingTime = -1;
            }
        }
        if (query.timestamp) {
            if (query.timestamp == 'asc') {
                orderBy.timestamp = 1;
            } else if (query.timestamp == 'desc') {
                orderBy.timestamp = -1;
            }
        }
        
        articles = await ArticleModel.find(searchBy)
        .where("state").equals("published")
        .limit(20)
        .sort(orderBy);
    } else {
        articles = await ArticleModel.find({ state: "published" })
        .limit(20);
    }
    
    return articles;
};
```
In the above function, we will perform the following actions;
Search the database for articles with a `state` property of `published`. Then we will filter the result by (author, title and tags). Next, we're going to sort the result in ascending or descending order by (readCount, readingTime and timestamp). Finally, we will limit the number of articles to be returned to 20 and return the result.

_Get Published Article by ID_

```javascript
const getPublishedArticleById = async(id) => {
    let article = await ArticleModel.findById(id);
    if (article && article.state == 'published') { //Confirm that the article is published
        const readCount = article.readCount + 1; //increment article read count by 1
        article = await ArticleModel.findByIdAndUpdate(id, {readCount}, ({new: true})); //Update article readCount and return the updated article
        //Return author information with the article
        const authorEmail = article.author;
        let author = await UserModel.findOne({ email: authorEmail });
        author = author.toObject();
        delete author.password; //Delete password from author information
        return {author, article};
    } 
}
```
As the name implies, the function above searches for a published article by its Id. If an the article is found, the value of the article's `readCount` property is incremented by 1. Then the article along with it's author are returned as objects.

_Create Article_

```javascript
const createArticle = async(body, userId) => {
    const article = new ArticleModel(body);
    article.author = userId;
    const readingTime = article.calculateReadingTime(body.body); //calculateReadingTime is a helper method in the articleModel
    article.readingTime = readingTime;
    article.save();
    return article;
}
```
This function uses the data from the request body to create a new article. It first initialize an article object, then it calculates the article's reading reading time before saving the new article to the database. Remember that `calculateReadtingTime` was defined as an instance method for the articles schema.

_Publish an Article_

```javascript
const publishOwnArticle = async(id, user) => {
    const article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        const article = await ArticleModel.findByIdAndUpdate(id, {state: "published"}, {new: true});
        if (!article) {
            return false;
        }
        return article;
    }
}
```
This function is responsible for changing the state of an article from draft (default) to published. It finds the article by the given Id, change the state and returns the updated article.

_Edit an Article_

```javascript
//Editable properties are title, description, body and tags
const editOwnArticle = async(id, body, user) => {
    const article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        let article = await ArticleModel.findByIdAndUpdate(id, body, {new: true});
        const readingTime = await article.calculateReadingTime(article.body); //Update readingTime
        article.readingTime = readingTime;
        article.save();

        if (!article) {
            return false;
        }
        return article;
    } else {
        return false;
    }
}
```
The function above can update multiple fields in an article, in a single request. The article is gotten by its Id property, its reading time is re-calculated to account for any change in length of the article body. The updated article is then resaved and also returned as an object.

Delete an Article

```javascript
const deleteOwnArticle = async(id, user) => {
    let article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        await ArticleModel.findByIdAndDelete(id); //Delete the article
        //Confirm that it was deleted successfully
        article = await ArticleModel.findById(id);
        if (!article) {
            return true; //Assert that the article has been deleted
        }
    } else {
        return false; //If the user is unauthorized
    }
}
```

_Delete an Article_

```javascript
const deleteOwnArticle = async(id, user) => {
    let article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        await ArticleModel.findByIdAndDelete(id); //Delete the article
        //Confirm that it was deleted successfully
        article = await ArticleModel.findById(id);
        if (!article) {
            return true; //Assert that the article has been deleted
        }
    } else {
        return false; //If the user is unauthorized
    }
}
```
As the name implies the above function gets an article by its ID and delete it permanently from the database.

_Get Articles owned by Logged-in User_

```javascript
const getOwnArticles = async(user, state) => {
    if (user) { //Ensure that the user is signed in
        const query = {};
        query.author = user;
        if (state) {
            query.state = state;
        } 
        const articles = await ArticleModel.find(query)
        .limit(20);
        return articles;
    } else return false;
}
```
This function gets only articles created by the user that is currently logged-in. The list of articles can be filtered by state and the final result is limited to 20 articles per page.

At the end of the file we exported all "services" functions in `article.service.js`.

Below is what `articles.service.js` would look like:

```javascript
/* src/services/articles.service.js */

const ArticleModel = require('../models/article.model');
const UserModel = require('../models/user.model');


const getPublishedArticles = async(query) => {
    let articles;
    if (query) {
        //
        const searchBy = {};
        const orderBy = {};
        searchBy.state = "published" //Ensure that only published articles are returned
        
        //Filter parameters (state, title and tags)
        if (query.author) {
            searchBy.author = query.author;
        }
        if (query.title) {
            searchBy.title = query.title;
        }
        if (query.tags) {
            let tags = query.tags.split(',');
            //Ensure that all the tags are in lowercase to match with structure of saved documents
            tags = tags.map((tag) => {
                return tag.toLowerCase();
            });
            searchBy.tags = {$in: tags};
        }

        //Order parameters (readCount, readingTime and timestamp)
        if (query.readCount) {
            if (query.readCount == 'asc') {
                orderBy.readCount = 1;
            } else if (query.readCount == 'desc') {
                orderBy.readCount = -1;
            }
        }
        if (query.readingTime) {
            if (query.readingTime == 'asc') {
                orderBy.readingTime = 1;
            } else if (query.readingTime == 'desc') {
                orderBy.readingTime = -1;
            }
        }
        if (query.timestamp) {
            if (query.timestamp == 'asc') {
                orderBy.timestamp = 1;
            } else if (query.timestamp == 'desc') {
                orderBy.timestamp = -1;
            }
        }
        
        articles = await ArticleModel.find(searchBy)
        .where("state").equals("published")
        .limit(20)
        .sort(orderBy);
    } else {
        articles = await ArticleModel.find({ state: "published" })
        .limit(20);
    }
    
    return articles;
};

const getPublishedArticleById = async(id) => {
    let article = await ArticleModel.findById(id);
    if (article && article.state == 'published') { //Confirm that the article is published
        const readCount = article.readCount + 1; //increment article read count by 1
        article = await ArticleModel.findByIdAndUpdate(id, {readCount}, ({new: true})); //Update article readCount and return the updated article
        //Return author information with the article
        const authorEmail = article.author;
        let author = await UserModel.findOne({ email: authorEmail });
        author = author.toObject();
        delete author.password; //Delete password from author information
        return {author, article};
    } 
}

const createArticle = async(body, userId) => {
    const article = new ArticleModel(body);
    article.author = userId;
    const readingTime = article.calculateReadingTime(body.body); //calculateReadingTime is a helper method in the articleModel
    article.readingTime = readingTime;
    article.save();
    return article;
}

const publishOwnArticle = async(id, user) => {
    const article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        const article = await ArticleModel.findByIdAndUpdate(id, {state: "published"}, {new: true});
        if (!article) {
            return false;
        }
        return article;
    }
}

//Editable properties are title, description, body and tags
const editOwnArticle = async(id, body, user) => {
    const article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        let article = await ArticleModel.findByIdAndUpdate(id, body, {new: true});
        const readingTime = await article.calculateReadingTime(article.body); //Update readingTime
        article.readingTime = readingTime;
        article.save();

        if (!article) {
            return false;
        }
        return article;
    } else {
        return false;
    }
}

const deleteOwnArticle = async(id, user) => {
    let article = await ArticleModel.findById(id);
    
    if (article.author == user) { //Ensure that the article belongs to the logged in user
        await ArticleModel.findByIdAndDelete(id); //Delete the article
        //Confirm that it was deleted successfully
        article = await ArticleModel.findById(id);
        if (!article) {
            return true; //Assert that the article has been deleted
        }
    } else {
        return false; //If the user is unauthorized
    }
}

const getOwnArticles = async(user, state) => {
    if (user) { //Ensure that the user is signed in
        const query = {};
        query.author = user;
        if (state) {
            query.state = state;
        } 
        const articles = await ArticleModel.find(query)
        .limit(20);
        return articles;
    } else return false;
}

module.exports = {
    getPublishedArticles,
    getPublishedArticleById,
    createArticle,
    publishOwnArticle,
    editOwnArticle,
    deleteOwnArticle,
    getOwnArticles,
}
```


## Bringing Everything together
So far, we have created all the various parts of our API, like server, database connection, models and routes. What we need to do next is to connect all the various parts together to work as one program.

**_Completing the code for `app.js`_**

```javascript
/* app.js */

const express = require('express');
const authRoute = require('./src/routes/auth.route');
const articlesRoute = require('./src/routes/articles.route');
require('./src/middlewares/auth.middleware');

//Create server
const app = express();

app.use(express.json()); //Parse payload

//Register routes middlewares to the app
app.use('/api', authRoute);
app.use('/api/articles', articlesRoute); //Home route redirects here

//The home route
app.use('/', (req, res) => {
    return res.status(200).send("Hello, welcome to Blog API. This API allow users to create or read articles created by others. Please Go to /api/articles to get a list of published articles or checkout the `README.md` file in the GitHub Repository to learn more about how it works and how to run or test it. Thank you!")
})

//Error Middleware
app.use((error, req, res, next) => {
    if (error) {
        return res.status(500).json({ message: "Internal Server Error." });
    } 
    next(); 
});

//Catch-all route
app.get('*', (req, res) => {
    return res.status(404).json({ message: "Not Found"});
});

module.exports = app;
```
We've made some changes to `app.js`. At the top, we imported `auth.route.js`, `article.route.js` and `auth.middleware.js`.
We used auth.route.js and article.route.js to create two middleware,  app.use('/api', authRoute) and app.user('/api/articles', articleRoute). These middleware are responsible for handling requests pertaining to user authentication and article management respectively.


**_The program entry-point_**
Create a new file `index.js` in the root directory. The is the file that gets executed when the program start.

```javascript
const app = require('./app');
require('dotenv').config();
require('./src/db'); //Connect to production database

const PORT = process.env.PORT;

//Start the server
app.listen(PORT, ()=> {console.log(`Server is running on port: ${PORT}`)});
```
We imported `app.js`, `dotenv` and db.js, which runs the database connection codes. Then we started the server by calling `app.listen`, which takes two arguments, `PORT` and a callback.

With these final steps, we are ready to run our program and start testing the different routes/endpoints in the Blog API.

**_Configure `package.json`_**

```javascript
{
  "name": "Blog_Api",
  "version": "1.0.0",
  "description": "This API has a general endpoint that shows a list of articles that have been created by different people, and anybody that calls this endpoint, should be able to read a blog created by them or other users.",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
  },
  "repository": {
    "type": "git",
    "url": ""
  },
  "author": "Sheriff Adekunle Tajudeen",
  "license": "ISC",
  "bugs": {
    "url": ""
  },
  "homepage": "",
  "dependencies": {
    "bcrypt": "^5.1.0",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "jsonwebtoken": "^8.5.1",
    "mongoose": "^6.7.0",
    "passport": "^0.6.0",
    "passport-jwt": "^4.0.0",
    "passport-local": "^1.0.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.20"
  }
}
```

**_Environment Variable_**
Define the following variables in `.env`:

```bat
PORT=8000
DATABASE_URI=Your database URL
JWT_SECRET=String for signing JWT Tokens
```
**_Remember that it is absolutely important that you keep your environment variables secret and never share them on any public platform._**


## Running the Program
Enter any of these commands in the terminal:
`npm run start`
`npm run dev`

If everything is fine up to this point your server should start running. On the console, copy the server URL. Paste and the URL in 
a browser and press enter. That should send a request to the home or `/` route.

If you want to test more endpoints, here is the [documentation](https://github.com/adekunleoloduero/altschool_second_semester_exam_blogging_api) for the project that inspired this tutorial. It was built as exam project for the second semester at [AltSchool Africa](https://www.altschoolafrica.com/).

## Conclusion
We have reached the end of this tutorial. I hope you were able to follow along up to this point. If you enjoyed the tutorial and found it useful, please like and share or leave a comment. I would also love to read any feedback you would like to share to help improve the content.

Thank you for reading.