Creating A Simple Blog Using MongoDB, Node-Js and Express

Creating A Simple Blog Using MongoDB, Node-Js and Express

Introduction

Hello guys, today with going to build a simple blog api from scratch using a Web Development Framework for Node Js which is Express Js and also MongoDB as our Database Management System(D.M.S), in this article you will see how the files are created, how the folders are being organised, important third party libraries that will be installed, how we will design the API structure and endpoints, how to plug in our blog app to the MongoDB database and also how to choose and implement an authentication strategy. I know it looks like a lot but trust me it is pretty straightforward as you read along.

Let's get coding

Prerequisites

The following are topics you must be familiar with beforehand

  • Basic Javascript Syntax and Terminologies

  • Command Line Interface (How to enter commands into the shell)

  • How to navigate your IDE (Integrated Development Environment) or Editor i.e VsCode, Atom, Sublime etc.

Also, below are the things you must first;

  • Install Node with its Package Manager

  • Install MongoDB or better yet set up your MongoDB Atlas

    API Structure

    This API will be able to

  • Register new users

  • Login existing users

  • Create new blogs posts

  • Edit and Update existing blog posts ( This action is restricted to the writer of the blog post)

  • Delete blog post (also restricted to writers of the blog)

  • Deliver all blog posts in a well-paginated format (all users can perform this action)

  • Get selected blog posts based on their ID (all users can also do this)

    Setup

    Let's get started with the Node.js code. Make a folder called Blog API.

    Then, in the terminal, type npm init -y to create a package.json file.

    After that, you must install some packages before proceeding.
    These packages will be used throughout the project.
    Install express, mongoose, dotenv, bcrypt, body-parser, jsonwebtoken in the API folder using the terminal.

    • Type the code npm install with the list of these packages

        npm install express mongoose dotenv bcrypt body-parser jsonwebtoken
      

      We will talk more about the functionalities of these packages as we move along.

      I am going to break down this project into steps tomake it easier to understand.

      Step one - Create an environmental variable in the .env file

        MONGODDB_URL = "mongodb+srv://username:password@cluster0.ki7ilh4.mongodb.net/blog_api?retryWrites=true&w=majority"
        PORT = 5050
        test_PORT = 4242
        JWT_SECRET = ksskx/oaapep0amakw0wruiq0amfmfm
        JWT_EXPIRY_TIME = 1h
      

      The environmental variables are codes, digits, and values that are to be kept secret or hidden from other people. As you can see the code contains my PORT, JWT_SECRET, and MongoDB CONNECTION URL. These are that users or other developers should have access to

### Step Two - Create a Configurations in the `config.js file`

```plaintext
require('dotenv').config();

module.exports = 
    { PORT: process.env.PORT || 8080,

        test_PORT: process.test_PORT,

MONGODB_URL:process.env.MONGODB_URL,

test_DB: process.env.test_DB,

JWT_SECRET: process.env.JWT_SECRET,

JWT_EXPIRY_TIME: process.env.JWT_EXPIRY_TIME,

}
```
  • After creating an environmental variable in the .env file we will need to configure them in such a way their values will not be exposed. These variables will still be used in other codes in this project, so configuration allows us to use the variables without disclosing their values.

    To help with that we are using a package called dotenv which we can see was required in the code.

    Step Three - Starting up the Server in the index.js file

    We will need to start up the server, so it can listen to requests and give back responses if needed. Setting up the server with express is quite straightforward.

      const express = require ("express");
      const app = express();
      const CONFIG = require ("../config")
    
      const PORT = CONFIG.PORT
    
      app.listen( PORT, () => {
          console.log(`Welcome to http//:localhost:${PORT}`)
      });
    

    We will still come back to the index.js file to explain somethings later

Step Four - Connecting to your Database in the db.js file

At this point, we are assuming you know how to your MongoDB either locally or using the MongoDB Atlas. If not, you can stop here and go on Google or Youtube to check out how it is being done, it's quite easy and will take you a few minutes to grasp and implement. Don't worry I can wait.

You are back already??!! That was quite fast so let's get right into it.

So you have been able to generate your MongoDB connection URL and you have successfully hidden it in your .env file (it supposes to be a secret, remember?).

Now we have to connect our server to the MongoDB database which we will achieve this using a package called mongoose (more on this later).

const mongoose = require("mongoose");
const config = require("../blog_api/config/config")
const mongodb_url = config.MONGODB_URL

function connectToMongoDB() {

mongoose.connect('mongodb+srv://Isaacadun:Isaakadun@cluster0.ki7ilh4.mongodb.net/blog_api?retryWrites=true&w=majority'
,{ useNewUrlParser: true, useUnifiedTopology: true })


 .then (console.log("MongoDB Connected"))
.catch((err) => console.log(err));
}

module.exports = { connectToMongoDB }

Step Five - Creating A Blog Schema in the Model Folder

A Model is a rule that outlines that the data is stored in a certain format, meaning if the data is not arranged in that specific format the data will not be accepted. It helps with consistency in storing our data. So now we need to create a model for our Blog in a blog.js file under the Model Folder (there's another blog.js file under the routes folder also).To create a Model, we will need an Object Data Modeling Package for our Database which for this project is MongoDb.Well, Luckily there is an ODM package tailored for MongoDb which is called mongoose.We will use mongoose to create a Blog Schema, a Schema is like a framework where the blog model will be listed in.

const mongoose = require("mongoose")
const User = require('../model/user')

const ObjectId = mongoose.Schema.ObjectId;

const BlogSchema = new mongoose.Schema({

    title:{
        type: String,
        required: true
    },
    desc:{
        type:String,
        required: false
    },
    author:{
        type: String,
        required: true,
    },
    author_id:{
        type: ObjectId,
        required: true,
    },
    tags:{
        type: String,
        required: true,
    },
    body:{
        type: String,
        required: true,

    },
    state: {
        type: String,
        required: true,
        enum: [ 'draft', 'published' ],
        default: "draft",
    },
    read_count: {
        type: Number,
        default: 0
        },

    read_time:{
            type: Number,

        } 
},
{ timestamps: true }
);

const BlogModel = mongoose.model('blogs', BlogSchema);

module.exports = BlogModel;

Step Six - Create a Blog Route where we can POST PUT DELETE GET

Our Server is set and ready to go, our database is ready to receive data and we have also created a model/format of how our blog data is to be stored. Let's now move on to setting up endpoint routes for CREATING a new blog, READING some or all of the blog, UPDATING the blog and DELETING the blog(this is popularly referred to as the CRUD functionality).

This will help us store blogs in our database using the POST HTTP request method, get all or some of the stored blogs using the GET request, and update the details of blogs using the PUT request, and also delete the blogs using the DELETE request.

const express = require("express");
const User = require("../model/user");
const Blogs = require("../model/blog");
const passport = require("passport")
const jwt = require("jsonwebtoken")
const blogRouter = express.Router();

//TO GET ALL BLOG

blogRouter.get("/",async (req,res)=> {

    const author = req.query.author; // trying to sort the get all post by authorname
    const tags = req.query.tags; //trying to sort the get all post by tags
    const title = req.query.title //trying to sort the get all post by title

    try {
        let blog;
        if(author){// I am throwing a condition that if the request has a query of author in it
            blog = await Blogs.find({author:author, state:"published"})// every request has a value(like this /?author:john) which we will have to find the post made by the author: john
        }
        else if(tags){// same here, throwing a condition if the request carries a query of cat(short for category) 
             blog = await Blogs.find({tags:tags, state:"published"}) //same thing happening here also note that you can paste the request query inside the conditions without creating a const
        }
        else if(title){
            blog = await Blogs.find({title:title, state:"published"}) // the curly braces in the bracket indicates that it's an object
        }
       else{ 
         blog = await Blogs.find({state:"published"});
        }
    //     blog.read_count += 1
    //    await blog.save()

        res.status(200).json(blog);
    }
    catch(err){
        res.status(500).json(err)
    }
});

//GET BLOG
blogRouter.get("/:id",async (req,res)=> {

    try {
        const { id }  = req.params
     const blog = await Blogs.findById({_id:id })

       blog.read_count += 1
       await blog.save()

        res.status(200).json(blog)
    }
    catch(err){
        res.status(500).json(err)
    }
})
;


//CREATE BlOG
blogRouter.post("/",passport.authenticate("jwt", { session: false }), async (req , res) =>{
    const newBlog = new Blogs({ ...req.body, author_id: req.user._id });
    try {
       const savedBlog  = await newBlog.save();
        res.status(200).json(savedBlog);

    } catch (err) {
        res.status(500).json(err)
    }
})


//UPDATE BLOG
blogRouter.put("/:id", passport.authenticate("jwt", { session: false }),  async (req,res) =>{

    try {
        const updateBlog = await Blogs.findByIdAndUpdate(req.params.id,{//here i used the Post model method to find the post and update it
            $set:req.body,// this is to set whatever is in the req.body as the updated post
        },
        { new: true } // this is for us to see our updated post
        );
        res.status(200).json(updateBlog);
} catch (err) {

    res.status(500).json(err)
}
 }
);

//DELETE BLOG
blogRouter.delete("/:id", passport.authenticate("jwt", { session: false }), async (req,res) =>{

     try {
         await Blogs.findByIdAndDelete(req.params.id)
         res.status(200).json("Post has been deleted!!");
 } catch (err) {

     res.status(500).json(err)
 }

});

module.exports = blogRouter

After creating these routes, we can create, read, update, and delete our blogs at will. Cool, right?. But at this point, anyone can delete or update any blog post without proper authorization. This is not so cool, so in this project, we would build a kind of authorization endpoint where clients have to be logged in to carry out certain actions on the app. Such as, posting, updating and deleting blogs, for us to do that users must first be registered and their details stored in the database. After which, when they try to log in, we will take their login details and compare them with that which is stored in a database.

If it's the details match they will be permitted to log in, we know this as authenticating the user. Before authentication can happen, the user's data must be stored in a database, which means we would have to also build a User Schema.

That brings us to the next step.

Step Seven - Creating a User Schema in the Model Folder

We already know what models and schemas are and how they are built, it's the same process and requires the same package which is mongoose

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

const Schema = mongoose.Schema;

const UserSchema = new Schema({
    firstname: {
        type: String,
        required: true,
    },
    lastname: {
        type: String,
        required: true,
    },
    email: {
        type: String,
        required: true,
        unique: true
    },
    password: {
        type: String,
        required: true
    }
});


// The code in the UserScheme.pre() function is called a pre-hook.
// Before the user information is saved in the database, this function will be called,
// you will get the plain text password, hash it, and store it.
UserSchema.pre(
    'save',
    async function (next) {
        const user = this;
        const hash = await bcrypt.hash(this.password, 10);

        this.password = hash;
        next();
    }
);

// You will also need to make sure that the user trying to log in has the correct credentials. Add the following new method:
UserSchema.methods.toCheckPassword = async function(password) {
    const user = this;
    const compare = await bcrypt.compare(password, user.password);

    return compare;
  }


const UserModel = mongoose.model('users', UserSchema);

module.exports = UserModel;

There is a little twist with this code, after listing out the format in which the users will be stored, we would have to encrypt the password so that no one even I would not be able to see it. The package that is used for encrypting passwords is called bcrypt