Weather app using Node.js and handlebars templating engine

Overview

We’ll learn how to create a simple weather app using the openWeatherMap API where the user will enter a ‘city’ and we will display the current temperature, weather condition, humidity value, and name of the city. Our goal here is to experiment with the integration of Node JS on the server-side and use handlebars as the templating engine. This will add variety to the syntax of templates, partials, and views that we’ll create to render the app on the webpage.

Create application

Let’s start by creating a new app in the code editor of our choice, we’re using VS code and we’ll navigate the terminal of our application and initialize the node by typing the following:

npm init

This will create a package.json file in the root of our app indicating the metadata (information) related to our app and all the dependencies required in this app will also be handled in this file.

Install required dependencies/packages

Next, we’ll install all the dependencies (npm packages) that we’ll use throughout the project. Their uses and the commands required to install are given below:

  • express: It is the standard server-side framework for Node JS. It is required to have this installed.
npm i express
  • handlebars template engine: The templating language framework that we will use to generate the HTML for our views/pages.
npm i hbs
  • request: We need the request module to make the HTTP calls and get the required data.
npm i request

Initial express setup

Now, we’ll create an src folder in the root directory of our app and create an app.js file there. We will require the dependencies and also place the initial code for the server to setup:

const express = require('express');
const req = require('express/lib/request');

const app = express();

const port = process.env.PORT || 3000;

// create a default route for our server
app.get('/', (req,res)=>{
    res.send('This is the default Route...')
})

app.listen(port, () => {
    console.log('Server is up and running on port: ', port)
});

The code above will make sure that the server is running on port 3000.

The openWeatherMap API key

Please note that for this app we have to login to openweathermap website and generate an API key which will be used in the base URL of this app. We have defined the base_url and secret key in a separate file called config.js in an object called constants:

Note: We should add our own secret_key generated from the openweathermap website in order to generate the weather data from the executable code widget at the bottom of this shot .

Get weather data

Now, we’ll create another file weatherData.js in the root directory and call the API to retrieve the current temperature, name of the city, weather description, and humidity. For this purpose, we’ll import the request module since we made an HTTP request to the openWeatherMap API and then fetch the data from API.

const request = require('request')
const constants = require('./config')

const weatherData = (address, callback) => {
    const url  = constants.openWeatherMap.BASE_URL + encodeURIComponent(address) + `&appid=` + constants.openWeatherMap.SECRET_KEY

request({url,json:true},(error,{body})=>{
    // console.log(body)
    if(error){
        callback(`Can't fetch the data`,undefined)
    } else {
        callback(undefined, {
           temperature: body.main.temp,
           description: body.weather[0].description,
           cityName:body.name
           
        })
    }
})
}

module.exports = weatherData;

The code above is getting the data from the API in a callback function and targeting the response object to extract the required data:

  • temperature: body.main.temp
  • description: body.weather[0].description
  • cityName:body.name

Results from weather data

Now, we can call the weatherData method in src/app.js to have access to the response.

const weatherData = require('../weatherData')

Also, we’ll define a /weather route here where we can console.log the results obtained:

// This is the Route to get the weather data
// localhost:3000/weather?address=chicago
app.get('/weather', (req,res) => {
    const address = req.query.address

    if(!address){
        return res.send({
            error: "Please enter a location to search weather"
        })
    }
   weatherData(address,(error, {temperature, description,cityName}) => {
       if(error){
           return res.send({
               error
           })
       } 
       console.log(temperature,description,cityName)
       res.send({
           temperature,
           description,
           cityName,
           
       })
   })
})

Views/partials

Also, we will be creating two folders under templates:

  • Partials: To create the partial to be used on different pages in our app like header/footer.
  • Views: To create the HTML for the homepage and other pages.

We need to specify the views and partials path in src/app.js as follows:

const hbs = require('hbs');
const path = require('path');

const port = process.env.PORT || 3000;

// specifying the path to our public folder having static assets
const publicStaticDirPath = path.join(__dirname,'../public')

const viewsPath = path.join(__dirname,'../templates/views')
const partialsPath = path.join(__dirname,'../templates/partials')

app.set('view engine','hbs');
app.set('views', viewsPath);
hbs.registerPartials(partialsPath)

app.use(express.static(publicStaticDirPath))

Note: We also have a public directory in the root of our app where we’ll define all the static assets like style.css and app.js.

Now, let’s move to the views folder and create an index.hbs file where we’ll be defining the HTML for the index page using handlebars templating engine.

<!DOCTYPE html>
<html>
    <head>
        <title>{{title}}</title>
        <link rel="stylesheet" href="css/style.css"/> 
    </head>
    <body>
        <div>
            {{!-- > refers to the partial: header --}}
            {{>header}}
            <article>
                <h3>Please enter the location below:</h3>
                <form><input placeholder="location..." type = "text"/><button>Seach</button>
                </form>
                <div>
                    <div><span></span></div>
                    <div>    
                        <div></div>
                         <div></div>    
                        <div></div>
                    </div>
                </div>
                <div></div>
            </article>
        </div>
        <script src="js/app.js"></script> <!-- absolute path -->
    </body>
</html>

DOM elements selection

We now move on to the app.js file which is created in the public/js folder where we’ll select the elements from the DOM and render the output on the screen.

Note: The style.css file is not included here but it can be downloaded from the style.css file from SPA code widget and include the styles to the app.

var fetchWeather = "/weather";

const weatherForm = document.querySelector('form');
const search = document.querySelector('input');


// const weatherIcon = document.querySelector('.weatherIcon i');
const weatherCondition = document.querySelector('.weatherCondition');

const tempElement = document.querySelector('.temperature span');

const locationElement = document.querySelector('.place');

const humidityElement = document.querySelector('.humidity');

const dateElement = document.querySelector('.date');

const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

dateElement.textContent = new Date().getDate() + ", " + monthNames[new Date().getMonth()].substring(0, 3) + " " + new Date().getFullYear();


weatherForm.addEventListener('submit', (event) => {
    event.preventDefault();
    
    locationElement.textContent = "City not found!";
    tempElement.textContent = "";
    weatherCondition.textContent = "";
    const locationApi = fetchWeather + "?address=" + search.value;
    
    fetch(locationApi).then(response => {
        response.json().then(data => {
            if(data.error) {
                locationElement.textContent = data.error;
                tempElement.textContent = "";
                weatherCondition.textContent = "";
            } else {
                locationElement.textContent ='City: ' + data.cityName;
                tempElement.textContent = (data.temperature - 273.15).toFixed(2) + String.fromCharCode(176);
                weatherCondition.textContent = 'Weather Condition: ' + data.description;
                
                weatherForm.reset();
            }
        }) 
    });
 
})

Explanation

The code here is self-explanatory in which we fetch the /weather route and render the output object into different divs on the webpage. Once the output is displayed, we reset the form so that the user can type in another search value.

Please note that the openWeatherMap API returns the temperature in Kelvin. Therefore we would have to subtract 273.15 from the output so that the temperature is displayed in Celsius.

We also display the current date by calling the javascript standard new Date() method. Since the getMonth() call will return a value from 0–11, we want to display the name of the month so we store the names of the months in an array and retrieve the name of the month from the array depending on the result of new Date().getMonth() method.

The locationApi key is actually the endpoint that includes the base route, such as /weather and then the location which needs to be passed as the query parameter. This will make a call to the weatherData function and return our desired result.

Code

const constants = {
    openWeatherMap:{
        BASE_URL:'https://api.openweathermap.org/data/2.5/weather?q=',
        SECRET_KEY:'c3ced497d87fe48e4beb953b02da8b21'
    }
}

module.exports = constants;
Code of Weather App using Node JS and handlebars templating engine

Free Resources

Attributions:
  1. undefined by undefined