Build News Website With Node.js, Express & EJS – WP Rest API + newsApi
Today we are going to build a simple News website/app using Node.js, Express, EJS and we’ll be also using some dependencies such as AXIOS, Body-Parser and Nodemon.
The website is going to have two main features which are Search and displaying the News Articles. I am going to keep it simple and straight to the point. No bootstrap and we will have a very minimal amount of CSS (SCSS).
The data for the articles will come from my personal website, but feel free to use whatever API you wish.
Here are some good suggestions:
- Your own WordPress site - Newsapi.org - Bing News API - Medium - Twitter
Before you start, you need to make sure that you have Node.js installed and have basic understanding of Node.js and Express. For more in information please watch the video.
Please note that my hosting is fairly slow and if too many of us use it for this tutorial it could potentially crash. I advice you to use one of the listed API’s above or use your own WordPress website. Alternatively you can try using mine. Links below:
WP Endpoints
https://raddy.co.uk/wp-json/wp/v2/posts/ https://raddy.co.uk/wp-json/wp/v2/posts?search=photoshop https://raddy.co.uk/wp-json/wp/v2/posts/5372 https://raddy.co.uk/wp-json/wp/v2/posts?_embed _embeded gives you more data to work with.
Initialize New Project
To initialise a new Node.js project all you have to do is to create a new project folder “news-app” and then run the Command line or PowerShell in the same directory. Once you do that to initialise a new project simply put the following command:
npm init
This will initialise a new project for you and it’s going to ask you a few questions about your project. The most important one is to give your project a name and then you can just keep pressing enter until the installation is over.
Project Structure
Now let’s create the following folders and files, leaving node_modules, readme.md, package-lock and package-json as that should have been automatically generated by now.
π node_modules π public π css π styles.css π styles.scss π img πΌ default.jpg π src π routes π news.ejs π views π news.ejs π newsSearch.ejs π newsSingle.ejs π README.md β .env π app.js π package-lock.json π package-json
Dependencies Installation
There are a few dependencies that we need to install to get started. Here is the list:
[x] Body-parser [x] Dotenv [x] EJS [x] Express [x] Axios
Let’s do that by opening the terminal / powershell and install the dependencies by typing the following command:
npm install ejs express body-parser dotenv axios
Restarting the local server
Restarting the server automatically would be annoying. To save us some time let’s quickly install Nodemon.
npm install --save-dev nodemon
To setup out application to run with nodemon just add the “start” line under scripts in your package.json file.
"scripts": {
"start": "nodemon app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Start our local server
To start our application / our local server simply type the following command in the command line:
npm start
Hopefully, everything should be working just fine and you won’t have any errors. Obviously, at this point, we haven’t yet started creating our website. Let’s do that.
Application
Let’s now create our application file. This file will be called app.js and it will sit in the root of our website.
In this file, we need to do a couple of things. We need to require some of the dependencies that we will be working with and we also need to set up our server.
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const port = 5000
// Static Files
app.use(express.static('public'))
// Templating Engine
app.set('views', './src/views')
app.set('view engine', 'ejs')
app.use(bodyParser.urlencoded({ extended : true }))
// Routes
const newsRouter = require('./src/routes/news')
app.use('/', newsRouter)
app.use('/article', newsRouter)
// Listen on port 5000
app.listen(port, () => console.log(`Listening on port ${port}`))
Views
Let’s start by building our home page/news page. In the views folder, you should have news.ejs file by now. Let’s create a very simple HTML file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js News</title>
<link rel="stylesheet" href="/css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet">
</head>
<body>
<header class="header">
<div class="header__logo">Node.Js News</div>
<%- include('./partials/search.ejs') %>
</header>
<div class="wrapper">
<div class="news">
<% if(articles != null) { %>
<% articles.forEach(function(article, index) { %>
<a href="/article/<%- article.id %>" class="news__card">
<img src="<%- article.thumbnail_url %>" alt="<%- article.title.rendered %>">
<h2><%- article.title.rendered %></h2>
<p><%- article.excerpt.rendered %></p>
</a>
<% }) %>
<% } else { %>
No posts found.
<% } %>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js News</title>
<link rel="stylesheet" href="/css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet">
</head>
<body>
<header class="header">
<div class="header__logo">Node.Js News</div>
<%- include('./partials/search.ejs') %>
</header>
<div class="wrapper">
<div class="news">
<% if(articles != null) { %>
<% articles.forEach(function(article, index) { %>
<a href="/article/<%- article.id %>" class="news__card">
<img src="<%- article.thumbnail_url %>" alt="<%- article.title.rendered %>">
<h2><%- article.title.rendered %></h2>
<p><%- article.excerpt.rendered %></p>
</a>
<% }) %>
<% } else { %>
No posts found.
<% } %>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js News</title>
<link rel="stylesheet" href="/css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet">
</head>
<body>
<header class="header">
<div class="header__logo">Node.Js News</div>
<%- include('./partials/search.ejs') %>
</header>
<div class="wrapper">
<div class="news-single">
<a href="/">β- Back</a>
<% if(article != null) { %>
<h2><%- article.title.rendered %></h2>
<p><%- article.content.rendered %></p>
<% } else { %>
No posts found.
<% } %>
</div>
</div>
</body>
</html>
Routes
const express = require('express')
const newsRouter = express.Router()
const axios = require('axios')
newsRouter.get('', async(req, res) => {
try {
const newsAPI = await axios.get(`https://raddy.co.uk/wp-json/wp/v2/posts/`)
res.render('news', { articles : newsAPI.data })
} catch (err) {
if(err.response) {
res.render('news', { articles : null })
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
} else if(err.requiest) {
res.render('news', { articles : null })
console.log(err.requiest)
} else {
res.render('news', { articles : null })
console.error('Error', err.message)
}
}
})
newsRouter.get('/:id', async(req, res) => {
let articleID = req.params.id
try {
const newsAPI = await axios.get(`https://raddy.co.uk/wp-json/wp/v2/posts/${articleID}`)
res.render('newsSingle', { article : newsAPI.data })
} catch (err) {
if(err.response) {
res.render('newsSingle', { article : null })
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
} else if(err.requiest) {
res.render('newsSingle', { article : null })
console.log(err.requiest)
} else {
res.render('newsSingle', { article : null })
console.error('Error', err.message)
}
}
})
newsRouter.post('', async(req, res) => {
let search = req.body.search
try {
const newsAPI = await axios.get(`https://raddy.co.uk/wp-json/wp/v2/posts?search=${search}`)
res.render('newsSearch', { articles : newsAPI.data })
} catch (err) {
if(err.response) {
res.render('newsSearch', { articles : null })
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
} else if(err.requiest) {
res.render('newsSearch', { articles : null })
console.log(err.requiest)
} else {
res.render('newsSearch', { articles : null })
console.error('Error', err.message)
}
}
})
module.exports = newsRouter
CSS
body {
margin: 0;
font-family: 'Source Sans Pro', sans-serif;
background-color: #f6f6f6;
}
img { max-width: 100%; }
h2 { font-size: 1.6rem; }
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
color: #fff;
background-color: #10555A;
margin-bottom: 10px;
&__search {
input[type=text] {
padding: 6px;
border: none;
}
input[type=submit] {
float: right;
padding: 6px 10px;
border: none;
cursor: pointer;
}
}
}
.wrapper { padding: 0 1rem }
.news {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
grid-gap: 2rem;
&__card {
text-decoration: none;
color: rgb(8, 8, 8);
background-color: #fff;
padding: 20px;
&:hover {
box-shadow: 0 3px 3px rgba(0,0,0,0.16), 0 3px 3px rgba(0,0,0,0.23);
}
}
}
.news-single {
background-color: #fff;
max-width: 1300px;
margin: 0 auto;
padding: 2rem;
}
API Development tool
Postman is a collaboration platform for API development. Postman’s features simplify each step of building an API and streamline collaboration so you can create better APIsβfaster.
Postman
The tool I was using in the video to Get data is Postman.
Examples:
Download
Thank you for reading this article. Please consider subscribing to my YouTube Channel.
Want to deploy your project for free on Heroku? Read the article
YouTube Questions
How to get and display the articles from the NewsApi.org
This example is only for the home (news.ejs) page. It’s more or less the same as the WordPress example, but just have to change the names. In this example, the link goes straight to the article as the content the API returns is not long enough to be on another page (in my opinion).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js News</title>
<link rel="stylesheet" href="/css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet">
</head>
<body>
<header class="header">
<div class="header__logo">Node.Js News</div>
<%- include('./partials/search.ejs') %>
</header>
<div class="wrapper">
<div class="news">
<% if(articles != null) { %>
<% articles.forEach(function(article, index) { %>
<a href="<%- article.url %>" class="news__card">
<img src="<%- article.urlToImage %>" alt="<%- article.title %>">
<h2><%- article.title %></h2>
<p><%- article.description %></p>
</a>
<% }) %>
<% } else { %>
No posts found.
<% } %>
</div>
</div>
</body>
</html>
The difference would be that we need to access the data object and then go into the articles. That’s pretty much it. This is only the GET Axios NewsApi example. Make sure that you add your API key.
newsRouter.get('', async(req, res) => {
try {
const newsAPI = await axios.get(`http://newsapi.org/v2/everything?q=bitcoin&from=2020-10-30&sortBy=publishedAt&apiKey= YOUR API KEY HERE`)
res.render('news', { articles : newsAPI.data.articles })
} catch (err) {
if(err.response) {
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
res.render('news', { articles : null })
} else if(err.requiest) {
res.render('news', { articles : null })
console.log(err.requiest)
} else {
res.render('news', { articles : null })
console.error('Error', err.message)
}
}
})
How to make Search work?
First we need to change the json file names so they match the NewsApi.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js News</title>
<link rel="stylesheet" href="/css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap" rel="stylesheet">
</head>
<body>
<header class="header">
<div class="header__logo">Node.Js News</div>
<%- include('./partials/search.ejs') %>
</header>
<div class="wrapper">
<div class="news">
<% if(articles != null) { %>
<% articles.forEach(function(article, index) { %>
<a href="<%- article.url %>" class="news__card">
<img src="<%- article.urlToImage %>" alt="<%- article.title %>">
<h2><%- article.title %></h2>
<p><%- article.description %></p>
</a>
<% }) %>
<% } else { %>
No posts found.
<% } %>
</div>
</div>
</body>
</html>
Then we need to swap the URL and go into the articles object:
newsRouter.post('', async(req, res) => {
let search = req.body.search
try {
const newsAPI = await axios.get(`http://newsapi.org/v2/everything?q=${search}&apiKey= YOUR API KEY HERE`)
res.render('newsSearch', { articles : newsAPI.data.articles })
} catch (err) {
if(err.response) {
res.render('newsSearch', { articles : null })
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
} else if(err.requiest) {
res.render('newsSearch', { articles : null })
console.log(err.requiest)
} else {
res.render('newsSearch', { articles : null })
console.error('Error', err.message)
}
}
})
That’s it. Search should be working.
Quick note: You might want to put something in place to prevent JS Injection Attacks.
Thank you for reading this article. Please consider subscribing to my YouTube Channel.
-
Pingback: Using Node.js with MySQL - CRUD | XAMPP / PhpMyAdmin - Raddy
-
Pingback: Deploy Node.js website on Heroku for Free - Raddy