DEV Community

Cover image for A Markdown Blog ( Node.js, Express, And MongoDB )
Tahzib Mahmud Rifat
Tahzib Mahmud Rifat

Posted on

A Markdown Blog ( Node.js, Express, And MongoDB )

31/3/24

INTRODUCTION

Simple

Setup Server

  1. At first we will create our package.json file with default values by giving

               npm init -y
    

npm: This is the Node Package Manager, a command-line tool used for managing Node.js packages and dependencies.

init: This is a command used to initialize a new Node.js project by creating a package.json file, which is a metadata file that contains information about the project, such as its name, version, description, dependencies, and more.

-y: This is a flag that stands for "yes." When used with npm init, it tells npm to automatically accept default values for all prompts and initialize the package.json file without requiring any user input.
Image description


2 . Now we are going to add express mongoDB and js.

         npm i express mongoose ejs
Enter fullscreen mode Exit fullscreen mode

The command npm i express mongoose ejs is used to install three Node.js packages: Express, Mongoose, and EJS.

Here's what each package does:

Express: Express is a fast, unopinionated, and minimalist web framework for Node.js. It provides a robust set of features for building web applications and APIs, including routing, middleware support, and template rendering.

Mongoose: Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a straightforward schema-based solution for modeling application data and interacting with MongoDB databases. Mongoose simplifies the process of defining schemas, validating data, and performing CRUD operations.

EJS: EJS (Embedded JavaScript) is a simple templating language that lets us generate HTML markup with plain JavaScript. It allows us to embed JavaScript code directly within our HTML templates, making it easy to create dynamic web pages by dynamically rendering data.

  • Now we are going to add dev dependencies and nodemon(it will help us for refresh our webpage automatically);

       npm i --save-dev nodemon
    

to run nodemon we have to add

   "devStart": "nodemon server.js"
Enter fullscreen mode Exit fullscreen mode

in our package.json file.

Image description

and now we are going to create a server.js file and run

            npm run devStart
Enter fullscreen mode Exit fullscreen mode

Create Index route

Image description

  app.set('view engine', 'ejs');
Enter fullscreen mode Exit fullscreen mode

Here, app.set is a method used to assign settings to our Express application. The 'view engine' setting is used to specify the template engine for your application.

'EJS' stands for Embedded JavaScript. It is a simple templating language/engine that lets us generate HTML markup with plain JavaScript.

  1. Now we are going to create a views folder and create index.ejs file and write some basic html code and access it from server.js file using,

    res.render('index');

Image description

Image description


Creating Article Routes

Image description

in our project there will lots of routes like, show, edit, delete and more. We are not going to access them from our server.js file. So that we are going to create routes folder, in that we are going to create article.js.

const express = require('expres');
const router = express.Router();

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

Image description

in server.js we can easily access article.js adding,

const articleRouter = require('./routes/articles');

app.use(articleRouter);

Image description

we want /article in front of every routes file. So that we can use

    app.use('/articles', articleRouter);
Enter fullscreen mode Exit fullscreen mode

this will give '/' to the articles.js file when search localhost:5000/articles . And in articles.js we can easily catch it using,

articleRouter is an instance of an Express router. A router is a middleware that allows you to group and handle routing to different parts of your application in a modular way. This router will handle all requests that start with /articles.

router.get('/', (req, res) => {
    res.send('This is from article.js');
})
Enter fullscreen mode Exit fullscreen mode

Image description


Pass Articles to Index

Currently we are passing only index but we want to pass all of our articles values.

const articles = [{
        title: 'Test Article',
        createdAt: Date.now(),
        description: 'Test description'
    }]
    res.render('index', {articles: articles});
Enter fullscreen mode Exit fullscreen mode

Image description

we are going to pass articles value in index.ejs later.

  • Now we are going to add bootstrap link to index.ejs file.

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    

and add New Article button.

<div class="container">
        <h1 class="mb-4">Blog Articles</h1> <!-- mb-4 stands for margin bottom of 4. -->

        <a href="/articles/new" class="btn btn-success">New Article</a> <!-- btn-success will give green color in the button.-->
    </div>
Enter fullscreen mode Exit fullscreen mode

Image description

Image description


Creating body

For body we are going to add a forEach method to index.ejs and make some change on server.js,

Image description

  • in index.ejs we are going to add ,
<div class="container">
        <h1 class="mb-4">Blog Articles</h1> <!-- mb-4 stands for margin bottom of 4. -->

        <a href="/articles/new" class="btn btn-success">New Article</a> <!-- btn-success will give green color in the button.-->

        <!-- for each will go through all the key value -->
        <% articles.forEach(article => {
            %>
            <div class="card mt-4"> <!-- Creating card-->
                <div class="card-body"> <!-- Card content-->
                    <h4 class="card-title"> <%= article.title %></h4>
                    <div class="card-subtitle text-muted mb-2">
                        <%= article.createdAt.toLocaleDateString() %>  <!-- this will create current date and the variable have to be new Date()-->
                    </div>

                    <!-- This is for text description-->
                    <div class="card-text mb-2">
                        <%= article.description %>
                    </div>
                </div>
            </div> 
            <%
        }) %>
    </div>
Enter fullscreen mode Exit fullscreen mode

Image description

  • And in server.js we updated the articles createdAt value,

    createdAt: new Date(),

Image description

Creating new article Route

so in the beginning we created New Article button. Now we are going to create new route for it.

  1. In article.js we updated the route.get,
router.get('/new', (req, res) => {
    res.render('articles/new');
})
Enter fullscreen mode Exit fullscreen mode

Image description

2 . Now we create new folder in views, named articles and in that we create a new file name new.ejs and move the index.ejs file to articles folder.

Image description

3 . we update the path in server.js,

  res.render('articles/index', {articles: articles});
Enter fullscreen mode Exit fullscreen mode

Image description

4 . In new.ejs we add,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- bootstrap link -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

    <title>Blog</title>
</head>
<body>
    <div class="container">
        <h1 class="mb-4">New Article</h1>

        <form action="/articles" method="POST">

        </form>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

but this new page is not finish yet, we have to some more. We need to create a sample format that give us same interface for New Article input and for Edit Article. So we are going to create a new file name _form_fields.ejs in the folder, views->articles-> _form_fields.ejs.
Image description

<%- this symbol helps use to render actual html, instead of text.

Now we are going to add this to our new.ejs file,

 <%- include('_form_fields') %>
Enter fullscreen mode Exit fullscreen mode

by this we can access _form_fields.ejs and what ever we write in form file it show to our new article page.
Image description

Now we write some demo,
Image description

and the output is,
Image description

Okey, now we are going to modify our form_fields file,

  • So our edited file of _form_fields.ejs now is,
<!-- This is for title-->
<div class="form-group">
    <label for="title">Title</label>
    <input required type="text" name="title" id="title" class="form-control"> <!-- form-control gives us the nice form fromat and the required give us the constraint of must fullfill the form-->
</div>

<!-- This is for description-->
<div class="form-group">
    <label for="description">Description</label>
    <textarea name="description" id="description" class="form-control"></textarea><!-- form-control gives us the nice form fromat and the required give us the constraint of must fullfill the form-->
</div>

<!-- This is for markdown-->
<div class="form-group">
    <label for="markdown">Markdown</label>
    <textarea name="markdown" id="markdown" class="form-control"></textarea><!-- form-control gives us the nice form fromat and the required give us the constraint of must fullfill the form-->
</div>

<!--cancel btn which will cancel editing and take us to home page-->

<a href="/" class="btn btn-secondary">Cancel</a>

<!-- Submit button-->
<button type="submit" class="btn btn-primary">Save</button>
Enter fullscreen mode Exit fullscreen mode

now this cancel btn will take us to home page and save btn will post the data to our server but we have to store the data to our database. So that we need to connect our database to our server.


Connecting Database

  1. At first we have to go to our server.js file. And add these lines,
- `const mongoose = require('mongoose');`
- `mongoose.connect('mongodb://localhost/blog');`
Enter fullscreen mode Exit fullscreen mode

that's it.
Image description

You may face different deprecations depending your mongodb version, like i don't face any depreciations, but if you face deprecations then you have to add it to ,

mongoose.connect('mongodb://localhost/blog', { useNewUrlPerser: true}); .

Image description

Now we are going to create mongoose model that will store the articles.


Mongoose Model

Image description

in article.js we are going to add these,

const mongoose = require('mongoose');

const articleSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    description: {
        type: String
    },
    markdown:{
        type: String,
        required: true
    },
    craetedAt: {
        type: Date,
        default: Date.now
    }
})

//to export the schema
module.exports = mongoose.model('Article', articleSchema)
Enter fullscreen mode Exit fullscreen mode

Image description

okey now we have to access it from
routes->articles.js,

  1. const Article = require('./../models/article')
    , This will have the path to models article.js

  2. Now we are going to create an object which will receive post data from the user,

const article = new Article({

    })
Enter fullscreen mode Exit fullscreen mode

Image description

but we have to tell express how to access this object. So that we have to go to server.js and add,

app.use(express.urlencoded({extended: false}))

Image description

after adding this to server.js , we can easily access title, description and markdown of new article from routes->articles.js by, req.body.title, req.body.description, req.body.markdown.

const express = require('express');

//access model article
const Article = require('./../models/article')
const router = express.Router();

router.get('/new', (req, res) => {
    res.render('articles/new');
})

router.get('/:id', (req, res) => {

})

router.post('/', async(req, res) =>{
    //to access the form of title, description and markdown we need to tell express how to access them
    const article = new Article({
        title: req.body.title,
        description: req.body.description,
        markdown: req.body.markdown
    })
    try{
        article = await article.save()
        res.redirect(`/articles/${article.id}`)
    }catch(e){
        res.render('articles/new', {article: article })
    }
})

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

after adding all these if we keep the markdown box empty then we click on save, it will show error on terminal.

Image description

Image description

Here the problem is in server.js file. we put the article router before the article object.
Image description

we have to put the article router after the urlencoded. So the updated on is,

Image description

so we put , app.use(express.urlencoded({extended: false})) this line top and move, app.use('/articles', articleRouter); this line to bottom.


Markdown

  1. Because we pass the articles data using ,res.render('articles/new', {article: article }) this.

Image description

we can easily grab those title, description and markdown data and use to our _form_fields.ejs file .

  • At first in title we updated the input portion, <input required value = "<%= article.title %>" type="text" name="title" id="title" class="form-control">

  • Then in description, we add in textarea, <textarea name="description" id="description" class="form-control"><% article.description %></textarea>

  • And the last in Markdown, we add same as description, <textarea name="markdown" id="markdown" class="form-control"><% article.markdown %></textarea>

Image description

2 . But after adding all these there is some error,

Image description

3 . To solve this we have to add {article: new Article()} this to articles.js.

Image description

in _form_fields.ejs we made the markdown as required. That means user have to fill this up.

Image description

4 . But these is some error available , if we click on save btn it will not save the data properly. So the solution is, in article.js we made the article variable constant tried to change its value later which is not possible. So put let instead of const.

Image description

5 . We can see some error while giving the input,
Image description

That's because we miss the = sign in <%= article.markdown %> and <%= article.description %> of _form_fields.ejs.

Image description


Render Article

  1. At first we are going to edit this portion of articles.js,
router.get('/:id', async (req, res) => {
    const article = await Article.findById(req.params.id)
    if(article == null) res.redirect('/')
    res.render('articles/show', {article: article })
})
Enter fullscreen mode Exit fullscreen mode

Image description

2 . we are going to create a new file name show.ejs in vies->articles-show.ejs

Image description

still i was facing some problem of typeerror of createdAT, i misspelled it on models->artile.js. Previously i typed craetedAt,

Image description

after fixing that,

Image description


Access All the Articles

Image description
So instead of these test data we are going to access the newly created data.

  1. We have to go to server.js. and we have to remove these portion, Image description

and we need to have the access of article model from server.js. its in models->article.js.

- `const Article = require('./models/article')`

- `const articles = await Article.find().sort({
    createdAt: 'desc' })`
Enter fullscreen mode Exit fullscreen mode

Image description

Image description


ADD SHOW BUTTON

we are going to create a show btn for our home page.

  1. At first we have to go to our index.ejs file. And add this,

    Image description

    now we are going to use slug which is the alternative of aticle_id which is slug.


    SLUG

    So we are going to import slug library

             npm i marked slugify
    

    marked: This is a package that allows us to parse Markdown in Node.js. It converts Markdown syntax into HTML.

    slugify: This package is used to convert a string into a slug, which is a URL-friendly version of the string. It removes special characters, converts spaces to dashes, and makes the string lowercase.

    So, when we run npm i marked slugify, npm will download and install both the marked and slugify packages into our project. We can then use these packages in our Node.js code to parse Markdown and create slugs, respectively.

    1. After that we go to models->article.js . And add,
    const marked = require('marked')
    const slugify = require('slugify') 
    

    plus,

    articleSchema.pre('validate', function(next) {
        if(this.title) {
            this.slug = sluggify(this.title, { lower: true,
            strict: true})
        }
    
        next()
    })
    

    Image description

    2 . So that to use the slug, we need to do some changes. at first in routes-articles.js,

    • router.get('/:slug', async (req, res) => added slug instead of id.

    • const article = await Article.find( { slug: req.params.id }) change the findById and add the slug portion.

    • res.redirect(/articles/${article.slug}) removed the id and added slug.

    But these is some error if you refresh the site.

    Image description

    3 . To solve this problem we have to go to views->articles->index.ejs and change,

    • <a href="articles/<%= article.slug %>" class="btn btn-primary">Read More</a> change the article.id to article.slug.

    and the problem still not solved. so we have to go to the routes->articles.js and change,

    • const article = await Article.findOne( { slug: req.params.id }) . we change the find to findOne.

    Image description

    Some type is fixed,

    1. in routes->articles.js : const article = await Article.findOne( { slug: req.params.slug })

    Image description

    1. And in models->article.js: this.slug = slugify(this.title, { lower: true,

    Image description

    After fixing all these there might show deprecations as it shows in the beginning . Please add those deprecations as we show before in database connect. Its all because of mongodb versions


    Create DELETE Route

    so we can see, only the news articles have the slugs added and the older ones shows nothing.

    Image description

    The old ones,
    Image description

    Different ones shows different.

    To solve these we are going to delete the older ones. To do that we have to create a DELETE Route.

    1. At first we have to go to our routes->articles.js and we need the DELETE method so, we have to import it. So that in terminal we use,

      npm i method-override

    The method-override package lets us use HTTP verbs such as PUT or DELETE in places where the client doesn't support it. This can be useful when handling form data in Express.js, as HTML forms only support GET and POST requests.

    Image description

    2 . To use this new method we have to go to server.js first. Then add these 2 lines,

    • const methodOverride = require('method-override')

    • app.use(methodOverride('_method'))

    Image description

    Create DELETE Form

    At first we have to go to our index.ejs file then,

    1.

    <form action="/articles/<%= article.id %>?_method=DELETE" method="POST" class="d-inline"><button type="submit" class="btn btn-danger">Delete</button></form>
    

    Image description

    and delete portion is done but there is some typo in routes->articles.js. please correct the spelling of redirect.

    Image description


    DOM Purify

    1. We need to import some packages,
    2. npm i dompurify jsdom

    3. we go to models->article.js , and add these on top,

    const createDomPurify = require('dompurify')
    const { JSDOM } = require('jsdom')
    const dompurify = createDomPurify(new JSDOM().window)
    

    Image description

    In image last we wrote createdomPurify which is misspelled. It should be createDomPurify.

    3 . Then we also add these,

    Image description

     sanitizedHtml: {
            type: String,
            required: true
        }
    

    and then add these,
    Image description

     if(this.markdown) {
            this.sanitizedHtml = dompurify.sanitized(marked(this.markdwon))
        }
    

    next we have to go to show.ejs and update,

    <%- article.sanitizedHtml %>

    Image description


    Edit Blog

    so we are in routes->articles.js .

    1. We add the route,
    router.get('/edit/:id', async (req, res) => {
        const article = await Article.findById(req.params.id)
        res.render('articles/edit', {article: article})
    })
    

    Image description

    and we add a new file to views->articles which is edit.ejs . And add,

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!-- bootstrap link -->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    
        <title>Blog</title>
    </head>
    <body>
        <div class="container">
            <h1 class="mb-4"> Edit Article</h1>
    
            <form action="/articles/<%= article.id %>?_method=PUT" method="POST">
                <%- include('_form_fields') %>
            </form>
        </div>
    </body>
    </html>
    

    Image description

    then we add the put method, because put and post methods are same so we are going to use a middleware.

    function saveArticleAndRedirect(path) {
        return async (req, res) => {
            let article = req.article
                article.title = req.body.title
                article.description = req.body.description
                article.markdown = req.body.markdown
            try{
                article = await article.save()
                res.redirect(`/articles/${article.slug}`)
            }catch(e){
                res.render(`articles/${path}`, {article: article })
            }
        }
    }
    

    Image description

    1. And we also update our POST method here,
    router.post('/', async(req, res, next) =>{
        req.article = new Article()
        next()    
    }, saveArticleAndRedirect('new'))
    

    Image description

    3 . So our post is done now , PUT .

    but after adding the method it stops working

    we will work on it later

Top comments (0)