DEV Community

Cover image for A blogging api
Temitope Agbaje
Temitope Agbaje

Posted on • Updated on

A blogging api

Introduction

This is a simple blogging API using Expressjs and MongoDB as the database. It allows authenticated users to access the blog post and also add blog posts.

These are the endpoints available on the API.

Methods Endpoints Description
POST /signup This is the signup endpoint
POST /login Logs an existing user
POST /blog/post this is to add a blog
GET /blog/post Gets all blog posts in the database
GET /blog/post/:id Get a single blog post using the id
PUT /blog/post/:id Updates the blog post
DELETE /blog/post/:id Deletes the blog post

Tools used

  • ExpressJs

  • MongoDB

  • Postman or Thunderclient ( a vscode extension)

Prerequisites

  • Code Editor (most preferably vscode)

  • Have Knowledge of Javascript and Nodejs

  • should have installed npm and nodejs

Let's code

Step 1: Set up your project folders

  • Create a folder, you can call it myBloggingApi

  • Open this folder in your code editor

  • In your code editor terminal run npm init, this initializes the npm package and creates a package.json file

my package.json file

  • Then install the libraries you need for the project using npm install <package-name>. Packages to install include Express, mongoose, dotenv, express, jsonwebtoken e.t.c

Step 2: Set up Database and Database connection

  • Login to MongoDB atlas or sign up if you have never signed up before

  • On the MongoDB atlas, click on Connect, then Connect your application, copy the link posted there and create a .env file and paste the link there with the name of MONGODB_CONNECTION_URI

MONGODB_CONNECTION_URI=mongodb+srv://TemitopeAgbaje:<password>.@cluster0.wzcue.mongodb.net/<name-of-database>?retryWrites=true&w=majority
Enter fullscreen mode Exit fullscreen mode
  • Then create a new folder in myBloggingApi folder, called database and then a file called db.js.

  • Paste this code into it

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

const MONGODB_CONNECTION_URI = process.env.MONGODB_CONNECTION_URI;

mongoose.set("strictQuery", false);

//connect to MongoDB
function connectToMongoDB() {
  mongoose.connect(MONGODB_CONNECTION_URI);

  mongoose.connection.on("connected", async () => {
    console.log("Connected to MongoDB successfully");
  });

  mongoose.connection.on("error", (err) => {
    console.log("Error connecting to MongoDB", err);
  });
}

module.exports = { connectToMongoDB };
Enter fullscreen mode Exit fullscreen mode

Remember to install the packages dotenv and mongoose. This code above helps you connect the database

Step 3: Create the index.js and server.js files

Index.js

  • Express package should have been installed

  • Install the package body-parser

const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());
app.use(express.static("public"));
app.use(express.urlencoded({ extended: false }));

app.get("/", (req, res) => {
  res.send("Welcome to my blog!");
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: "Something broke!" });
});

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

Server.js

  • Add the PORT to .env folder
require("dotenv").config(); //process.env
const app = require("./index");
const db = require("./config/db")

const PORT = process.env.PORT || 4500;
db.connectToMongoDB() //connect to mongoDB


app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Write the Schema

  • Create a new folder in myBloggingApi folder, and name it models, then create the files for userModel.js and blogModel.js

userModel.js

  • Install bcrypt package
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const Schema = mongoose.Schema;

const userSchema = new Schema({
  first_name: { type: String, required: true },
  last_name: { type: String, required: true },
  email: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
  },
});

userSchema.pre("save", async function (next) {
  const hashedPassword = await bcrypt.hash(this.password, 10);
  this.password = hashedPassword;
  next();
});

userSchema.methods.isPassword = async function (inputedPassword) {
  const isCorrectPassword = await bcrypt.compare(
    inputedPassword,
    this.password
  );
  return isCorrectPassword;
};

const User = mongoose.model("User", userSchema);

module.exports = User;
Enter fullscreen mode Exit fullscreen mode
  • userSchema.pre method using bcrypt helps hash the password before saving it to the database

  • userSchema.methods.isPassword method using bcrypt helps to compare the password

blogModel.js

const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const blogSchema = new Schema({
  title: {
    type: String,
    required: "Please add a title",
  },
  description: String,
  author: String,
  state: {
    type: String,
    enum: ["draft", "published"],
    default: "draft",
  },
  read_count: { type: Number, default: 0 },
  reading_time: String,
  tags: Array,
  body: {
    type: String,
    required: "Please add a body",
  },
  timestamp: Date,
});

const Blog = mongoose.model("Blog", blogSchema);

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

Step 5: Create the auth middleware

  • Create a new folder in myBloggingApi folder, and name it middleware then create the file auth.js

  • In your vscode terminal, install passport and passport-jwt package

const passport = require("passport");
const JWTStrategy = require("passport-jwt").Strategy;
const ExtractJWT = require("passport-jwt").ExtractJwt;
require("dotenv").config();

passport.use(
  "jwt",
  new JWTStrategy(
    {
      jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    },
    (payload, done) => {
      try {
        done(null, payload.user);
      } catch (err) {
        done(err);
      }
    }
  )
);
Enter fullscreen mode Exit fullscreen mode

Step 6: Create Controllers

  • Add a new folder with the name Controllers and create the authContoller.js and blogController.js files

  • Run npm install jsonwebtoken in your vscode terminal

authContoller.js

const jwt = require("jsonwebtoken");
const User = require("../models/userModel");

exports.signup = async (req, res) => {
  const userExistInDB = await User.findOne({ email: req.body.email });

  if (userExistInDB) {
    return res.status(409).json({ message: "Email exists" });
  }

  const user = await User.create(req.body);

  user.password = undefined;
  const payload = { user };

  const token = jwt.sign(payload, process.env.JWT_SECRET, {
    expiresIn: "1h",
  });
  return res.status(201).json({
    user,
    token,
  });
};

exports.login = async (req, res, next) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email: email });
blogController
  if (!user) {
    return res.status(400).json({message: "User does not exist"})
  }

  const isCorrectPassword = await user.isPassword(password);
  if (!isCorrectPassword) {
    return res.status(400).json({message: "Incorrect Password"})
  }

  user.password = undefined;
  const payload = { user };
  const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: "1h" });
  return res.status(200).json({ user, token });
};
Enter fullscreen mode Exit fullscreen mode

blogController.js

const blogModel = require("../models/blogModel");
const moment = require("moment");

exports.createBlogPost = async (req, res) => {
  const text = req.body.body;
  const wpm = 225;
  const words = text.trim().split(/\s+/).length;
  const time = Math.ceil(words / wpm);

  const blogPost = await blogModel.create({
    author: req.body.author,
    description: req.body.description,
    timestamp: moment().toDate(),
    reading_time: `${time} mins`,
    title: req.body.title,
    body: req.body.body,
    tags: req.body.tags,
  });
  return res.status(201).json({ status: "New Post", blogPost });
};

exports.getBlogPosts = async (req, res) => {
  const { query } = req;

  const {
    timestamp,
    state,
    page = 1,
    per_page = 20,
    read_count,
    reading_time,
    author,
    title,
    tags,
  } = query;

  let findQuery = {};

  if (timestamp) {
    findQuery.timestamp = {
      $gt: moment(timestamp).startOf("day").toDate(),
      $lt: moment(timestamp).endOf("day").toDate(),
    };
  }

  if (state) {
    findQuery.state = state;
  }

  if (read_count) {
    findQuery.read_count = read_count;
  }

  if (reading_time) {
    findQuery = {
      ...findQuery,
      reading_time: { $regex: reading_time, $options: "i" },
    };
  }

  if (author) {
    // findQuery.author = author;
    findQuery = { ...findQuery, author: { $regex: author, $options: "i" } };
  }

  if (title) {
    findQuery = { ...findQuery, title: { $regex: title, $options: "i" } };
  }

  if (tags) {
    findQuery = { ...findQuery, tags: { $in: tags } };
  }

  const blogPosts = await blogModel
    .find(findQuery)
    .skip(page - 1)
    .limit(per_page);
  return res.status(200).json({ status: "All Post Loaded", blogPosts });
};

exports.getBlogPost = async (req, res) => {
  try {
    const { id } = req.params;

    const blogPost = await blogModel.findById({ _id: id });

    if (!blogPost) {
      return res.status(404).json({ status: "Blog Post Not found" });
    }

    if (blogPost.state === "published") {
      blogPost.read_count = blogPost.read_count += 1;
    }

    await blogPost.save();

    return res.status(200).json({ status: "Blog Post Loaded", blogPost });
  } catch (err) {
    return res.status(400).json({ message: "Bad Request" });
  }
};

exports.updateBlogPost = async (req, res, next) => {
  const { id } = req.params;
  const blogPost = await blogModel.findById({ _id: id });

  if (!blogPost) {
    return res.status(400).json({ message: "No blog Post" });
  }

  if (req.body.state) {
    blogPost.state = req.body.state;
  }

  if (req.body.author) {
    blogPost.author = req.body.author;
  }

  if (req.body.body) {
    blogPost.body = req.body.body;
  }

  if (req.body.tags) {
    blogPost.tags = req.body.tags;
  }

  await blogPost.save();

  return res.status(200).json({ status: "Post Updated", blogPost });
};

exports.deleteBlogPost = async (req, res) => {
  const { id } = req.params;

  const blogPost = await blogModel.findById({ _id: id });

  if (!blogPost) {
    return res.status(400).json({ message: "No blog Post" });
  }

  await blogPost.delete();

  return res.status(200).json({ status: "Deleted successfully"});
};
Enter fullscreen mode Exit fullscreen mode

Step 7: Routes

  • In myBoggingApi folder, add routes folder and in it we have the authRoute.js and blogRoutes.js

authRoute.js

const express = require("express");


const AuthController = require("../controllers/authController");

const authRouter = express.Router();

authRouter.post("/signup", AuthController.signup);

authRouter.post("/login", AuthController.login);

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

blogRoutes.js

const express = require("express");
const BlogController = require("../controllers/blogController");

const blogRouter = express.Router();

blogRouter.post("/post", BlogController.createBlogPost);

blogRouter.get("/posts", BlogController.getBlogPosts);

blogRouter.get("/post/:id", BlogController.getBlogPost);

blogRouter.put("/post/:id", BlogController.updateBlogPost);

blogRouter.delete("/post/:id",BlogController.deleteBlogPost);

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

Step 8: Updating index.js

  • securing the blog route

  • initialize passport

const express = require("express");
const passport = require("passport");
const blogRouter = require("./routes/blogRoutes");
const authRouter = require("./routes/authRoutes");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());
// app.use(express.json());
app.use(express.static("public"));
app.use(express.urlencoded({ extended: false }));

app.use(passport.initialize());
require("./middlewares/auth");

app.get("/", (req, res) => {
  res.send("<h2>Welcome to my blog!</h2>");
});

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

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: "Something broke!" });
});

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

Conclusion

I hope with this article you can create your blogging API.

I hosted it with render, Click here to try it out

Link to the GitHub repository:https://github.com/TemitopeAgbaje/blogging-api

Top comments (0)