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

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

31/3/24

INTRODUCTION

Simple

Setup Server

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.

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

npm i express mongoose ejs

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”

in our package.json file.

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

npm run devStart

Create Index route

app.set(‘view engine’, ‘ejs’);

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.

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’);

Creating Article Routes

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 ;

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

const articleRouter = require(‘./routes/articles’);

app.use(articleRouter);

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

app.use(‘/articles’, articleRouter);

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’);
})

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});

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>

Creating body

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

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>

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

createdAt: new Date(),

Creating new article Route

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

In article.js we updated the route.get,

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

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.

3 . we update the path in server.js,

res.render(‘articles/index’, {articles: articles});

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>

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.

<%- 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’) %>

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

Now we write some demo,

and the output is,

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>

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

At first we have to go to our server.js file. And add these lines,

– `const mongoose = require(‘mongoose’);`
– `mongoose.connect(‘mongodb://localhost/blog’);`

that’s it.

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}); .

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

Mongoose Model

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)

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

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

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

const article = new Article({

})

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}))

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 ;

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

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

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

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

Markdown

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

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>

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

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

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

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.

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

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

Render Article

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 })
})

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

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

after fixing that,

Access All the Articles


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

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

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’ })`

ADD SHOW BUTTON

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

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

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.

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()
})

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.

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.

Some type is fixed,

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

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

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.

The old ones,

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.

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.

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’))

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>

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

DOM Purify

We need to import some packages,

npm i dompurify jsdom

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)

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

3 . Then we also add these,

sanitizedHtml: {
type: String,
required: true
}

and then add these,

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

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

<%- article.sanitizedHtml %>

Edit Blog

so we are in routes->articles.js .

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})
})

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>

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 })
}
}
}

And we also update our POST method here,

router.post(‘/’, async(req, res, next) =>{
req.article = new Article()
next()
}, saveArticleAndRedirect(‘new’))

3 . So our post is done now , PUT .

but after adding the method it stops working

we will work on it later

Leave a Reply

Your email address will not be published. Required fields are marked *