Creating a Dynamic Blog with Node.js, Express, and EJS: A Comprehensive Guide - Part 1
- Ctrl Man
- Web Development , Backend , JavaScript
- 08 Jul, 2024
Creating a Dynamic Blog with Node.js, Express, and EJS: A Comprehensive Guide (Part 1)
Introduction
In the ever-evolving landscape of web development, it’s crucial to choose tools that are versatile, efficient, and support dynamic content creation. Among these, Node.js, Express, and EJS stand out as powerful options for crafting engaging, interactive blogs. This two-part guide will walk you through setting up and developing your own dynamic blog using these technologies, ideal for web developers at the beginning of their journey.
Table of Contents for Part 1
- Why Node.js, Express, and EJS?
- Setting Up the Project
- Creating the Basic Express Server
- Implementing EJS Templates
- Creating Routes for the Blog
- Implementing the Blog Functionality
Why Node.js, Express, and EJS?
- Node.js: Known for its non-blocking I/O model, Node.js enables fast, efficient server-side JavaScript execution. It’s particularly suitable for building real-time applications like chat apps or blogs that require quick response times.
- Express: This is a minimal and flexible web application framework for Node.js. It provides robust routing capabilities, making it simple to organize blog posts by categories or tags.
- EJS: An embedded JavaScript template system, EJS offers a powerful way to create dynamic content within your blog. By embedding script in HTML templates, you can easily add logic to generate personalized user experiences.
Setting Up the Project
Installing Node.js and npm
Ensure your development environment has Node.js (version 14.x or later) installed and use npx
or npm install
command to manage dependencies.
# Install Node.js
curl -sL https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
# Verify installation of Node.js
node --version
# Initialize npm project
npm init -y
npm install express ejs body-parser mongodb dotenv
Creating the Basic Express Server
Setting up the main app.js
file
Let’s create a robust app.js
:
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const MongoClient = require('mongodb').MongoClient;
const app = express();
// Middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static('public'));
// Set view engine
app.set('view engine', 'ejs');
// Database connection
const url = process.env.MONGODB_URI || 'mongodb://localhost:27017';
const dbName = 'myblog';
MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true })
.then(client => {
console.log('Connected to Database');
global.db = client.db(dbName);
})
.catch(err => console.error(err));
// Routes
const blogRoutes = require('./routes/blog');
app.use('/', blogRoutes);
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
Implementing EJS Templates
Creating a views
folder and basic layout
Create a views
folder with layout.ejs
and index.ejs
:
<!-- views/layout.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<%- include('partials/header') %>
<div class="container mt-4">
<%- body %>
</div>
<%- include('partials/footer') %>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
<!-- views/index.ejs -->
<%- contentFor('body') %>
<h1>Welcome to My Dynamic Blog!</h1>
<% if(posts.length > 0) { %>
<% posts.forEach(function(post) { %>
<div class="card mb-3">
<div class="card-body">
<h2 class="card-title"><%= post.title %></h2>
<p class="card-text"><%= post.content.substring(0, 100) %>...</p>
<a href="/post/<%= post._id %>" class="btn btn-primary">Read More</a>
</div>
</div>
<% }); %>
<%- include('partials/pagination') %>
<% } else { %>
<p>No blog posts available.</p>
<% } %>
Creating Routes for the Blog
Create a routes
folder with blog.js
:
// routes/blog.js
const express = require('express');
const router = express.Router();
const ObjectId = require('mongodb').ObjectId;
// Home page route
router.get('/', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = 10;
const skip = (page - 1) * limit;
const posts = await global.db.collection('posts').find().skip(skip).limit(limit).toArray();
const total = await global.db.collection('posts').countDocuments();
res.render('index', {
title: 'Home Page',
posts: posts,
currentPage: page,
pages: Math.ceil(total / limit)
});
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
// Individual blog post route
router.get('/post/:id', async (req, res) => {
try {
const post = await global.db.collection('posts').findOne({ _id: new ObjectId(req.params.id) });
if (!post) {
return res.status(404).send('Post not found');
}
res.render('post', { title: post.title, post: post });
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
// Search route
router.get('/search', async (req, res) => {
try {
const keyword = req.query.keyword || '';
const posts = await global.db.collection('posts').find(
{ $text: { $search: keyword } }
).toArray();
res.render('index', { title: 'Search Results', posts: posts });
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
module.exports = router;
Implementing the Blog Functionality
Creating and Editing Posts
Add routes for creating and editing posts:
// Add to routes/blog.js
// Render create post form
router.get('/create', (req, res) => {
res.render('create', { title: 'Create New Post' });
});
// Handle post creation
router.post('/create', async (req, res) => {
try {
const { title, content, tags } = req.body;
const result = await global.db.collection('posts').insertOne({
title,
content,
tags: tags.split(',').map(tag => tag.trim()),
createdAt: new Date()
});
res.redirect(`/post/${result.insertedId}`);
} catch (error) {
console.error(error);
res.status(500).send('Error creating post');
}
});
// Render edit post form
router.get('/edit/:id', async (req, res) => {
try {
const post = await global.db.collection('posts').findOne({ _id: new ObjectId(req.params.id) });
if (!post) {
return res.status(404).send('Post not found');
}
res.render('edit', { title: 'Edit Post', post: post });
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
// Handle post update
router.post('/edit/:id', async (req, res) => {
try {
const { title, content, tags } = req.body;
await global.db.collection('posts').updateOne(
{ _id: new ObjectId(req.params.id) },
{ $set: { title, content, tags: tags.split(',').map(tag => tag.trim()) } }
);
res.redirect(`/post/${req.params.id}`);
} catch (error) {
console.error(error);
res.status(500).send('Error updating post');
}
});
This concludes Part 1 of our guide on creating a dynamic blog with Node.js, Express, and EJS. In Part 2, we’ll cover adding image uploads, styling the blog, implementing user authentication and comments, addressing security considerations, testing, and deployment.