Building a simple blog application with node.js
Published: 2023-01-10 18:05:26


This is a walkthrough of I built a basic blog application using Express framework and MongoDB in nodejs. It is a simple CRUD application using the MVC pattern. This article is written with the assumption that the reader has an intermediate knowledge of JavaScript and nodejs, and so I will be skipping some granular details.

### **Software requirements**

The first step in this project was to gather all the requirements needed for the functionality of the blog. The API should have endpoints where;

1. New users can register.
2. Existing users can log in.
3. Only logged in users can create new blog posts.
4. Both logged in and not logged in users can get all published blogs.
5. Only logged in users can create new blog posts.
6. Only logged in users can edit a post
7. Only logged in users can edit a post.

### Project Setup
 

After gathering the requirements, it was time to set up my project. I used VS Code as my Integrated Development Environment(IDE).

I created a .env file for environmental variables, which is added to git.ignore file to prevent accidental exposure of secrets
I installed the necessary packages, set up my server to listen at a dedicated port and connected to the database. Read [this article ](https://medium.com/developers-compass/how-to-create-your-first-server-in-express-js-bd7bf1910925) on how to set up a server in Express. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fznt8ydtaulglu52dnv.PNG)

Next I created a Data Access Object(DAO) or models for the user and the blog posts, based on the requirements. This is used to retrieve or send data to the database.

##### Blog Model

```
const mongoose = require("mongoose");

const {readingTime } = require("../utils/readingTime")

const BlogSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },

  description: {
    type: String,
    required: true

  },

  content: {
    type: String,
    required: true
    
  },

  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
    
  },

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

  read_count: {
    type: Number,
    default: 0,
  },

  reading_time: {
  type: Number
},

  tags: [String],


  postedAt: {
    type: String,
    default: new Date().toString(),
  },
});


BlogSchema.pre('save', function (next) {
  let blog = this

  if (!blog.isModified('content')) return next()

  const timeTaken = readingTime(this.content)

  blog.reading_time = timeTaken
  next()
})

module.exports = mongoose.model("Blog", BlogSchema, "Blogs");
```

##### User Model

```
const mongoose = require('mongoose')
const bcrypt = require('bcrypt')
const validator = require('validator')

const Schema = mongoose.Schema;
const ObjectId = Schema.ObjectId;

const userSchema = new Schema({
  id: ObjectId,

  first_name: {
    type: String,
    required: true
  },

  last_name: {
    type: String,
    required: true
  },

  author_name: {
    type: String
    
  },

  username: {
    type: String,
    required: true
    
  },

  email: {
    type: String,
    required: true,
    unique: true
  },

  password: {
    type: String,
    required: true
    },

  blogs: [{type: Schema.Types.ObjectId, ref: "Blog"}]
});


userSchema.statics.signup = async function(first_name, last_name, username, email, password) {

  if (!first_name || !last_name || !username || !email || !password) {
    throw Error('All fields must be completed!')
  }

  const isinUse = await this.findOne({ email })

  if (isinUse) {
    throw Error('Email already exists')
  }

  const salt = await bcrypt.genSalt(10)
  const hashedPassword = await bcrypt.hash(password, salt)
  
  const author_name = first_name.concat(" ", last_name);

  const user = await this.create({ first_name, last_name, author_name, username, email, password: hashedPassword })

  return user
}

// static login method
userSchema.statics.login = async function(username, password) {

  if (!username || !password) {
    throw Error('All fields must be filled')
  }

  const user = await this.findOne({ username })
  if (!user) {
    throw Error('Incorrect username')
  }

  const matchPassword = await bcrypt.compare(password, user.password)
  if (!matchPassword) {
    throw Error('Incorrect password')
  }

  return user
}

module.exports = mongoose.model('User', userSchema)

```


Then, I created a service containing the methods needed for the endpoints for both users and blog posts. These will call the DAO methods.

```

const router = require("express").Router();
const Blog = require("../models/blogModel");


const getBlogs = async () => {
    const blogs = await Blog.find().populate('author');
    return blogs;
  };

const getSpecificBlog = async (blogId) => {
    const blog = await Blog.findById(blogId);
    return blog;
  };



const editBlog = async (blogId, title, description, content) => {
    const editedBlog = Blog.updateOne({ _id: blogId }, { title, description, content })
    return editedBlog;
  };


const deleteBlog = async (blogId) => {
    const deletedBlog = await Blog.findByIdAndRemove(blogId);
    return deletedBlog;
  };



module.exports = {
    getBlogs,
    getSpecificBlog,
    editBlog,
    deleteBlog
};

```
Next is to set up the controllers that will call the service methods. Controllers serve as the middlemen between route handlers and services.

```
const blogService= require("../services/blog.service")

const getAllBlogs = async (req, res) => {
    const blogs = await blogService.getBlogs();
    res.json({ data: blogs });
  };


const getSpecificBlog = async (req, res) => {
    const { id } = req.params
    const blog = await blogService.getSpecificBlog(id);
    res.json({ data: blog });
  };


const editBlog = async (req, res) => {
    const { id } = req.params;
    const { title, description, content } = req.body;
    const blog = await blogService.editBlog(id, title, description, content);
    res.json({ data: blog });
  };


const deleteBlog = async (req, res) => {
    const { id } = req.params
    const deletedblog = await blogService.deleteBlog(id);
    res.json({ data: deletedblog });
  };



 module.exports= {
    getAllBlogs,
    getSpecificBlog,
    editBlog,
    deleteBlog
};
```

Then I created the route handlers which will pass the request objects and related metadata to the controllers.

```
const {getAllBlogs, 
    getSpecificBlog,
    editBlog, 
    deleteBlog}= require("../controllers/blog.controller");

const express = require('express');


const router = express.Router();


router.route("/")
    .get(getAllBlogs);


router.route("/:id")
    .get(getSpecificBlog)
    .put(editBlog)
    .delete(deleteBlog);


    module.exports= router
```

Then I called the routes in the server.js file.

```
const express= require("express");
const db = require("./middleware/db");
blogRoutes= require("./routes/blog.routes")

require("dotenv").config()

PORT= process.env.PORT;

const app = express()

app.use(express.json())
app.use(express.urlencoded({extended:true}));


db.connectToMongoDB();

app.use('/blog', blogRoutes);

app.listen(PORT, () => {
    console.log(`Server is listening at PORT ${PORT}`)
})
```

Check out the full project here on Github