Raddy The Brand Official Website

Build News Website With Node.js, Express & EJS – WP Rest API + newsApi

By Raddy in NodeJs ·

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 advise 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.js
 πŸ“‚ 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 [deprecated]
[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:

You no longer need to install the body-parser. It comes with Express

npm install ejs express 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 the 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')

// Parsing middleware
// Parse application/x-www-form-urlencoded
// app.use(bodyParser.urlencoded({ extended: false })); // Deprecated
app.use(express.urlencoded({extended: true})); // New



// 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> 

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.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.