Implementing your Blog API Designs
Published: 2023-01-10 20:00:00

After going through the API design and features we'd be implementing in our previous [article](https://dev.to/jhhornn/designing-our-blog-api-3gdb?preview=8aa9b1453be771a08ea0001cf70ef70c57f9f834c7bfd07dc87fc40b1b6379599a7c647beb1c4e933f819449560e1335a9e02db8db063049f416a92f), let us delve straight into the full implementation in codes


![brace up codes ahead](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14kraz99wi4wdbm2ib2d.png)

### Setting up our Server

Before we set up our server, let's take a look at connecting to our database. In our `config` folder, let's create a `database.js` file and copy and paste the code below:

```javascript
const mongoose = require("mongoose")
const dotenv = require("dotenv")
dotenv.config({ path: "./config/.env" })

const MONGODB_URI =
  process.env.NODE_ENV === "test"
    ? process.env.TEST_MONGODB_URI
    : process.env.MONGODB_URI

const connectDB = async () => {
  await mongoose
    .connect(MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    })
    .then((conn) => {
      console.log(
        `Conected to Mongo! Database name: ${conn.connections[0].name}`
      )
    })
    .catch((err) => console.error("Error connecting to mongo", err))
}

module.exports = connectDB
```
The `dotenv` package allows us to read values from the `.env` file we created earlier using the `process.env.VARIABLE_NAME` command and `mongoose` is an ORM that helps relate with the MongoDB database. The MONGODB_URI is gotten from mongodb atlas. You can check out this [video](https://www.youtube.com/watch?v=sx2zTNiaBuM) for refesher. The database is exported as a module to be used in another file.

Let's link this up with our server code shown below. 

```javascript
const app = require("./app")
const connectDB = require("./config/database")
const http = require("http")
const path = require("path")
const dotenv = require("dotenv")
dotenv.config({ path: path.join(__dirname, "./config/.env") })

const PORT = process.env.PORT || 4040
const server = http.createServer(app)

connectDB().then(() => {
  server.listen(PORT, async () => {
    console.log(`Server running on port ${PORT}`)
  })
})
```
The `connectDB()` function is called and on resolving, the server is spawned. This ensures that we're connected to the database first in order to interact with it

This is a server running on our local machine and on the value of `PORT` from our `.env` file or `4040`. The `app` in our server is the heart of all of our logic and this is where all the several pieces that makes up our blog API come together.

Having talked about setting up our server and connecting to the database, we also want to know who our database looks like, how the information collected are stored in an organised `Schema` created with `mongoose`

### Models
Two Schemas(`blog.js` and `users.js`) were created for this project in the `models` folder in our project folder structure. One for the Blog and one for Users.

The Blog Model:
```javascript
const mongoose = require("mongoose")

const Schema = mongoose.Schema
const objectId = Schema.Types.ObjectId

const BlogSchema = new Schema(
  {
    title: {
      type: String,
      required: [true, "Please enter a title"],
      unique: true
    },
    description: {
      type: String,
      required: [true, "Please enter a description"]
    },
    owner: {
      type: String
    },
    author: {
      type: objectId,
      ref: "User"
    },
    state: {
      type: String,
      default: "draft",
      enum: ["draft", "published"]
    },
    readCount: {
      type: Number
    },
    readingTime: {
      type: Number
    },
    tags: [String],
    body: {
      type: String,
      required: [true, "Please provide the blog content"]
    }
  },
  { timestamps: true }
)

//! Add the blog reading time before saving
BlogSchema.pre("save", function (next) {
  let blog = this
  let titleLength = blog.title.length
  let descriptionLength = blog.description.length
  let bodyLength = blog.body.length
  let totalLength = titleLength + descriptionLength + bodyLength
  let totalTime = Math.round(totalLength / 200)

  blog.readCount = 0
  blog.readingTime = totalTime == 0 ? 1 : totalTime

  blog.tags = blog.tags.map((tag) => tag.toLowerCase())

  next()
})

//! Delete certain fields before returning result to client
BlogSchema.set("toJSON", {
  transform: (document, returnedObject) => {
    delete returnedObject.__v
  }
})

const Blog = mongoose.model("Blog", BlogSchema)

module.exports = Blog
```

The User Model:

```javascript
const mongoose = require("mongoose")
const bcrypt = require("bcrypt")

const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId

const UserSchema = new Schema(
  {
    email: {
      type: String,
      required: true,
      unique: true
    },
    firstName: {
      type: String,
      required: true
    },
    lastName: {
      type: String,
      required: true
    },
    password: {
      type: String,
      required: true
    },
    repeatPassword: {
      type: String,
      required: true
    },
    userType: {
      type: String,
      default: "user",
      enum: ["admin", "user"]
    },
    blogs: [
      {
        type: ObjectId,
        ref: "Blog"
      }
    ]
  },
  { timestamps: true }
)

//! Encrypt password before saving it to database
UserSchema.pre("save", function (next) {
  let user = this

  if (!user.isModified("password")) return next()

  bcrypt.hash(user.password, 8, (err, hash) => {
    if (err) return next(err)
    user.password = hash
    user.repeatPassword = hash
    next()
  })
})

//! Compare inputted password with the password in the database
UserSchema.methods.comparePassword = function (pword) {
  const passwordHash = this.password
  return new Promise((resolve, reject) => {
    bcrypt.compare(pword, passwordHash, (err, same) => {
      if (err) {
        return reject(err)
      }
      resolve(same)
    })
  })
}

//! Delete certain fields before returning result to client
UserSchema.set("toJSON", {
  transform: (document, returnedObject) => {
    returnedObject.id = returnedObject._id.toString()
    delete returnedObject._id
    delete returnedObject.__v
    delete returnedObject.password
    delete returnedObject.repeatPassword
  }
})

const User = mongoose.model("user", UserSchema)
module.exports = User
```

Both models represent the model of the information we want to recieve from our users and store in our database. The User model is used for `authentication`. They both also have `pre hooks` that run just before the data is saved to the database. The user password is `hashed` or `encrypted` before it is saved to the database using the `bcrypt` package. The user model has a `method` that is also used to compare user's inputted password with that in the database when trying to login.

The next thing we want to talk about are the endpoints but before then, let's look at some of the middleware codes we implemented so we can get the flow of request objects through the middlewares and the eventual request handler or controller.

Let's create a `middleware` folder as in our file structure and put inside the folder we create the `auth.js`, `filter.js` and `paginate.js`. This leads us to explaining the implementation of the middleware features discussed in the [previous](LINK TO PREVIOUS ARTICLE) article.

### Authentication
This middleware code is contained in the `auth.js` file we just created. It contains the following codes:
```javascript
const passport = require("passport")
const localStrategy = require("passport-local").Strategy
const UserModel = require("../models/users")

const JWTstrategy = require("passport-jwt").Strategy
const ExtractJWT = require("passport-jwt").ExtractJwt

const path = require("path")
const dotenv = require("dotenv")
dotenv.config({ path: path.join(__dirname, "../config/.env") })

const { BadRequestError, UnauthenticatedError } = require("../errors")

const opts = {}
opts.secretOrKey = process.env.JWT_SECRET
opts.jwtFromRequest = ExtractJWT.fromAuthHeaderAsBearerToken()

module.exports = (passport) => {
  passport.use(
    new JWTstrategy(opts, async (token, done) => {
      try {
        const user = token.user
        return done(null, user)
      } catch (err) {
        done(err)
      }
    })
  )

  passport.use(
    "signup",
    new localStrategy(
      {
        usernameField: "email",
        passwordField: "password",
        passReqToCallback: true
      },
      async (req, email, password, done) => {
        try {
          const { firstName, lastName, repeatPassword } = req.body
          const userObject = {
            firstName,
            lastName,
            email,
            password,
            repeatPassword
          }

          const user = new UserModel(userObject)
          const savedUser = await user.save()

          return done(null, savedUser)
        } catch (err) {
          done(err)
        }
      }
    )
  )

  passport.use(
    "login",
    new localStrategy(
      {
        usernameField: "email",
        passwordField: "password"
      },
      async (email, password, done) => {
        try {
          if (!email || !password) {
            throw new BadRequestError("Please provide email or password")
          }
          const user = await UserModel.findOne({ email })

          if (!user) {
            throw new UnauthenticatedError("Please provide valid credentials")
          }

          const validate = await user.comparePassword(password)

          if (!validate) {
            throw new UnauthenticatedError("Please provide valid credentials")
          }

          return done(null, user, { message: "Logged in Successfully" })
        } catch (err) {
          done(err)
        }
      }
    )
  )
}
```
Passport handles our authentication using the `JWT strategy` (a token based authentication). It also allows us to use the `local Strategy` to run bothe the sign up and login feature of the application. The JWT strategy allows for a `token` to be generated after it has been verified to release a `payload` containing information that can be used to access the next request handler.

### Filter
This middleware code is contained in the `filter.js` file we just created. It contains the following codes:
```javascript
const filterByPublished = (req, res, next) => {
  req.filterObject.state = "published"
  return next()
}

const filterAndSort = (req, res, next) => {
  const { tag, author, title, state, sort } = req.query
  req.filterObject = {}
  req.sortObject = {}
 

  try {
    if (tag) {
      const searchTag = Array.isArray(tag)
        ? tag.toLowerCase()
        : tag.split(", " || " " || ",").map((t) => t.toLowerCase())
      req.filterObject.tags = { $in: searchTag }
    }
    if (author) {
      req.filterObject.owner = { $regex: author, $options: "i" }
    }
    if (title) {
      req.filterObject.title = { $regex: title, $options: "i" }
    }

    if (sort) {
      const sortList = sort.split(",").join(" ")
      req.sortObject.sort = sortList
    } else {
      req.sortObject.sort = "createdAt"
    }

    if (state) {
      req.filterObject.state = state
    } else {
      req.filterObject.state = { $in: ["draft", "published"] }
    }

    return next()
  } catch (err) {
    next(err)
  }
}

module.exports = {
  filterByPublished,
  filterAndSort
}
```
When we try to search for published blogs based on certain criteria such as authors, tags, titles, we have to add `query` to our request. These queries are collected in this middleware and passed on to the next request handler in order to be used.

### Paginate

This middleware code is contained in the `paginate.js` file we just created. It contains the following codes:

```javascript
const paginate = (req, res, next) => {
  req.paginate = {}
  const blogsPerPage = 20
  try {
    if (req.query.p) {
      const page = req.query.p
      const numOfBlogsToSkip = (page - 1) * blogsPerPage

      req.paginate.blogsPerPage = blogsPerPage
      req.paginate.numOfBlogsToSkip = numOfBlogsToSkip
      return next()
    }
    const page = 1
    let numOfBlogsToSkip = (page - 1) * blogsPerPage

    req.paginate.blogsPerPage = blogsPerPage
    req.paginate.numOfBlogsToSkip = numOfBlogsToSkip
    return next()
  } catch (err) {
    next(err)
  }
}

module.exports = paginate
```

The paginate middleware sends the `number of blogs per page` and `number of blogs to skip per page` to the next request handler.

Now that we are done with the middlewares that preceed our controllers, let us talk about our Routes and Controllers.

### Routes
Let's not forget the summary of the endpoints in our previous article in this series. We are going to be linking the routes in our endpoints to a particular controller and/or middleware
Let's take a look

The Blog Routes

```javascript
const express = require("express")
const passport = require("passport")
const {
  createBlog,
  getAllBlogs,
  updateBlog,
  updateBlogState,
  getAllUsersBlogs,
  getBlogById,
  deleteBlog,
  getBlogByIdAuth
} = require("../controllers/blog")
const { filterAndSort, filterByPublished } = require("../middlewares/filter")
const paginate = require("../middlewares/paginate")
const validateBlog = require("../validators/blog.validator")

const blogRouter = express.Router()

blogRouter
  .route("/home/blog")
  .get(filterAndSort, filterByPublished, paginate, getAllBlogs)
blogRouter
  .route("/home/blog/:id")
  .get(filterAndSort, filterByPublished, getBlogById)

blogRouter.use("/blog", passport.authenticate("jwt", { session: false }))

blogRouter
  .route("/blog")
  .get(filterAndSort, paginate, getAllUsersBlogs)
  .post(validateBlog, createBlog)

blogRouter
  .route("/blog/:id")
  .get(filterAndSort, getBlogByIdAuth)
  .put(validateBlog, updateBlog)
  .patch(updateBlogState)
  .delete(deleteBlog)

module.exports = blogRouter
```

The blog route makes use of the the express [Router()](https://expressjs.com/en/5x/api.html#router) method and we can see how it links our various routes to middlewares and controllers. It is important to note that the order of the middlewares are important and should be followed strictly. The user route follows the same pattern.

The input validator used in thr `validateBlog` function is a middleware that helps validate the user input withe the help of a package known as [joi](https://joi.dev/api/?v=17.7.0). You can check the validator folder in the source code link belw for it implementation.

### The Controllers
They handle all of our CRUD operations. There are quite a number of controllers in this project and I would be picking three of them to talk about. One that requires authentication, one that does not then one that requires `parameter`.

The POST or create a new blog controller
```javascript
const createBlog = async (req, res, next) => {
  const { id } = req.user
  const { title, description, body, tags } = req.body

  const user = await UserModel.findById({ _id: id })

  try {
    const blog = new BlogModel({
      title,
      description,
      owner: `${user.firstName} ${user.lastName}`,
      body,
      tags,
      author: id
    })

    const savedBlog = await blog.save()

    user.blogs = user.blogs.concat(savedBlog.id)
    await user.save()

    return res.status(201).json(savedBlog)
  } catch (err) {
    next(err)
  }
}
```
The `req.user` is the payload handed down from the authentication middleware and we can see how we used our BlogModel to create a new blog and at the same time save the reference ID of the blog to the list of blogs created by that user.

The GET all published blog controller
```javascript
const getAllBlogs = async (req, res, next) => {
  const filters = req.filterObject
  const { sort } = req.sortObject
  const { blogsPerPage, numOfBlogsToSkip } = req.paginate

  try {
    const blog = await BlogModel.find(filters, { title: 1, description: 1 })
      .sort(sort)
      .skip(numOfBlogsToSkip)
      .limit(blogsPerPage)

    return res.status(200).json({ count: blog.length, blogs: blog })
  } catch (err) {
    next(err)
  }
}
```
This controller does not require authentication and we have the filterObject, sortObject and paginate attached to requests coming their respective middlewares. It returns just the `title` and `description` of all the blogs.

The GET blog published blog by ID controller

```javascript
const getBlogById = async (req, res, next) => {
  try {
    const { state } = req.filterObject
    const { id } = req.params
    const blog = await BlogModel.findOneAndUpdate(
      { _id: id, state: state },
      { $inc: { readCount: 1 } },
      { new: true }
    )

    if (!blog) {
      throw new NotFoundError(`No blog with id ${id} to update`)
    }

    return res.status(200).json(blog)
  } catch (err) {
    next(err)
  }
}
```
Getting blog by `id` requires that a parameter is added to the route in order to open that specific blog.


Everything we have discussed individually are brought together in the `app.js` file which can be checked in the source code provided below.

### Wrapping Up

All of the codes above were abstracted from the main [source code](https://github.com/jhhornn/naija-diary) and should not be run separately on their own. The source code should be checked to further understanding and also to be able to link how all the modules work hand in hand.

As time goes on, specific parts of the code will be spoken upon with reference to this.

Let's talk about testing and deployment in our next article.