Share this blog!

Hello all!

This is part 2 of the article series of building a simple JavaScript weather App.

In part 1, we discussed about the API design perspectives and the architecture of the application. In this article, we will be discussing about the backend implementation of the application, which is essentially about implementing the API we discussed previously.

In this tutorial, we will be:

  1. Initiating a node project
  2. Mapping API endpoints to functions
  3. Configuration and connecting to a database
  4. Querying the database and return result
  5. Testing the API

Things we will NOT be talking about and act as prerequisites:

  • Installing node, MySQL etc.
  • Setting up MySQL or generating mock data
  • Installing packages


A quick recap from the last article, the API we came up with has the following endpoints and use cases:

  1. GET   /locations - Retrieve list of locations that can be forecasted ordered alphabetically by the city
  2. GET   /conditions - Retrieve list of supported weather conditions
  3. GET /forecasts - Retrieve weather forecast of a city defined by query parameter, responding with an array of 10 forecast items. (Optional "limit" query parameter can define the expected forecast count)


Initializing the project

You can simply use npm init to start a node project.

In addition to the usual node project structure, I have added some extra packages so that the services, db management and configurations are separately stored.

Project 
|-------api 
|        |-------swagger.yaml
|-------config 
|        |-------config.js 
|-------db 
|        |-------db.js 
|-------node_modules 
|-------service 
|        |-------condition.service.js 
|        |-------forecast.service.js 
|        |-------location.service.js 
|-------.gitignore 
|-------index.js 
|-------package-lock.json 
|-------package.json

Swagger definition

The content of swagger.yaml can be found in this gist (https://gist.github.com/sachi-d/0f2c0af614723aab0c30095fdfcbe93c) and it was added to the project as a reference to the API definition we came up with. 

You can use swagger to generate the server code from the yaml, but in this tutorial, we will be writing the API functionality by hand simply because our API is not that complicated.

Index.js

The index.js file contains the mappings of the API endpoints and it is essentially the starting point of the application. 

You may notice that the endpoints are mapped into functions in the service layer and finally, the app is listening to the port defined in the configuration. Note how the headers are being used to enable CORS.


const express = require('express');
const app = express();
const mysql = require('mysql');
const config = require('./config/config.js');
const db = require('./db/db.js');
global.db = db;


const {getLocations} = require('./service/location.service');
const {getConditions} = require('./service/condition.service');
const {getForecasts, getForecastsByID} = require('./service/forecast.service');

//enable CORS
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

//map the endpoints with the functions
app.get('/locations', getLocations);
app.get('/conditions', getConditions);
app.get('/forecasts', getForecasts);


const port = config.port;
app.listen(port, () => console.log(`Listening on port ${port}..`));

Configuration file

My config.js file content are as follows, but you can add any kind of configuration/variable options in this, so that you can easily access/update them on the run.

'use strict'

const config = {
 port: process.env.PORT || 8080,
 db: {
  host: 'localhost',
  user: 'mydb-username',
  password: 'mydb-password',
  database: 'mydb-database-name',
  multipleStatements: true
 }
}

module.exports = config

Database connection

I used the db.js file as the database manager, to handle the connection creation.

const mysql = require('mysql');
const config = require('./../config/config.js');

const db = mysql.createConnection (config.db);

// connect to database
db.connect((err) => {
    if (err) {
        throw err;
    }
    console.log('Connected to database');
});

module.exports = db;

Endpoints

I used the service package to collect the data from the database and then expose them through the endpoint. Alternatively, you can achieve more coherency by using a separate DAO layer to retrieve the data and then using a service layer to handle the business logic.

The following code represents the SQL query execution, which retrieves the list of supported weather conditions and locations and then responds to the API requests with the collected data. 


condition.service.js


module.exports = {
 getLocations: (req, res) => {

  //retrieve a list of available locations
  let query = "SELECT * FROM `location` ORDER BY city";

  // execute query
  db.query(query, (err, result) => {
   if (err) {
    console.log(err);
    res.status(400).send("An unexpected error occurred while retrieving locations.");
    return;
   }
   res.send(result);
   return;
  });
 }
};


location.service.js


module.exports = {
 getConditions: (req, res) => {
  //returns a list of supported weather condition names and IDs
  let query = "SELECT * FROM `weathercondition` ORDER BY id ASC";

  db.query(query, (err, result) => {

   if (err) {
    console.log(err);
    res.status(400).send("An unexpected error occurred while retrieving weather conditions");
    return;
   }
   res.send(result);
   return;
  });
 },
};



forecast.service.js

The forecast endpoint implementation seems like the most complicated of all, but when you break down the functionalities, you can easily understand what is happening in the code.

The endpoint requires the location as a query parameter which should be defined by the city, region and the country, separated by commas (inspiration was from Yahoo weather API).

For example,

/weaptherAPI/forecasts?location=Sydney,NSW,Australia 

is a valid API call while

/weaptherAPI/forecasts?location=Sydney,Australia 
/weaptherAPI/forecasts?location=NSW,Australia 
/weaptherAPI/forecasts?location=Sydney

are invalid API calls.

Optionally, the limit query parameter can be used to limit the number of results retrieved, if limit is not defined, 10 results will be returned by default.

When analysing the following code, you may observe how the query parameters are parsed and validated. I have used error messages to handle validation failures. If validations are successful, the parameters will be fed into the SQL query which would return the results as an array.


module.exports = {
 getForecasts: (req, res) => {
  //returns a list of weather forecasts for the specified location

  //if limit is not set, return 10 results by default
  const limit = req.query.limit || 10;
  if(isNaN(limit)){
   res.status(400).send('Invalid request: Invalid limit parameter');
   return;
  }

  const location = req.query.location;

  //location is a required parameter
  if (!location){
   res.status(400).send('Invalid request: Missing location parameter');
   return;
  }


  const parts = location.split(",");

  if(parts.length != 3){
   res.status(400).send('Invalid location parameter');
   return;
  }
  const city = parts[0].trim();
  const region = parts[1].trim();
  const country = parts[2].trim();


  let query = `SELECT w.*, c.conditionName FROM
          (SELECT weather.* FROM weather, location WHERE idLocation = location.id
     AND location.city = "${city}"
     AND location.region = "${region}"
     AND location.country = "${country}"
            AND forecastDate >= UNIX_TIMESTAMP(NOW() - INTERVAL 1 DAY) * 1000
            ORDER BY forecastDate
            LIMIT ${limit}) as w
          LEFT JOIN
            (SELECT id as conditionID, name as conditionName FROM weathercondition) c
            ON w.idCondition = c.conditionID`;


  // execute query
  db.query(query, (err, result) => {
   if (err) {
    console.log(err);
    res.status(400).send("An unexpected error occurred while retrieving weather forecasts");
    return;
   }
   if(result.length == 0){
    res.status(404).send(`No data found for ${location}`);
    return;
   }
            res.send(result);
  });

 }
};



Testing the API

Now your application is ready to run. Use node index.js to start the application in your machine and since all the endpoints are GET type, you can simply use the browser to view the results. 

Access the following URLs to view your results in the browser:

  • http://localhost:8080/locations
  • http://localhost:8080/conditions
  • http://localhost:8080/forecasts?location=any+location+you+have+dummy+data+of

When implementing an API that can be called by a third party application, it is quite important to handle all possible scenarios. These scenarios include both valid and invalid inputs which need to be handled carefully in your implementation. In order to test your application, it is essential to list down the test scenarios that are applicable. The following depicts some of the test scenarios that need to be tested against our application.



Once the test scenarios are identified, testing needs to be carried out. This can be done either manually or programmatically, which we will talk about in a future article.

That's it for the the backend implementation and feel free to share anything you would find useful in the comment section.

Cheers!


Introduction

Hello all! 

This will be the first of the article collection that we will be going through to implement a simple weather app using JavaScript for the full stack. The weather app would have:

1. A Rest API that would give us current and forecasted weather for predefined locations
2. A web application that would display the current and forecasted weather of a selected location

To achieve the above, we would need to make some decisions. 

Since we are going to pursue everything using JS, we could easily use NodeJS to implement a backend server that would deploy the Rest API. For the front end app, we do have a lot of choices, but in this tutorial, we will be using React. We also need some sort of DB integration to read the weather data from. To limit the scope of this tutorial, let's skip how the data is entered and worry only about reading data from the database. 

In this tutorial, we will be talking about the following topics:

1. Part 1 - The architecture and API design
4. Part 4 - Front end implementation ~ Components


In Part 1, we will be discussing about the architecture and the design considerations of the API. We will be talking about:

  1. A proposed architecture for the backend and client apps
  2. A little introduction to Rest APIs
  3. Proposed API endpoints and their parameters and responses

The Architecture

As mentioned above, the purpose of this tutorial is to implement a simple weather app using JavaScript. It might sound like one big app, but it could be easily reusable and made more independent if we have 2 different apps for the backend and frontend respectively. Implementing a Rest API would basically mean the same thing.

In terms of the data, we could integrate a database and expose the data in the Rest API, which can be called by any third party app and display the data any way they like. Think of our backend as something similar to the Yahoo or the OpenWeatherMap API.

So the architecture I would go for would be as follows:


As you can see, the 3 main components would be our backend, frontend and the DB. 

Starting the implementation of the client app first would not be a very good idea, because you would not have an idea about the data you would be getting from the APIs. So the best course of actions (or the TODO list) would be as follows:

1. Design the APIs
2. Implement the APIs (backend)
3. Implement the client App

So let's get ready to tick-off our first to-do item!

API Design

If you're an expert on Rest APIs, you may skip the following section, but everyone else, gather up for a very brief introduction to Rest APIs and the bits of information you should absolutely know about Rest APIs.

A RESTful API is simply an application program interface (API) that uses HTTP requests to exchange data (communicate) between components. These communications can be of the following types:
  • GET - read data
  • PUT - update data
  • POST - insert data
  • DELETE - delete data

Restful APIs should always comply to the following constraints:
  1. Use of a uniform interface (UI) - The purpose of Rest APIs is to communicate with servers to get/update information on resources. And this constraint simply means that any resource should be defined using URIs (uniform resource identifiers) that should be self descriptive. For example, if an API is used to retrieve details of a book "/books/1234" is resource based and is a better design than "books/the+book+I+bought+today" as the latter doesn't seem like a uniform identifier for a book.
  2. Client-server based - There should be a clear delineation between the client and server. UI and request-gathering concerns are the client’s domain. Data access, workload management and security are the server’s domain. This loose coupling of the client and server enables each to be developed and enhanced independent of the other.
  3. Stateless operations - All client-server operations should be stateless, and any state management that is required should take place on the client, not the server.
  4. RESTful resource caching - All resources should allow caching unless explicitly indicated that caching is not possible.
  5. Layered system - REST allows for an architecture composed of multiple layers of servers. So it is valid if a client app doesn't know if he is directly calling a server or if it goes through multiple layers that would finally land on the server.
  6. Code on demand - Most of the time a server will send back static representations of resources in the form of XML or JSON. However, when necessary, servers can send executable code to the client.

Now let's think about how the weather APIs could be used and what kind of data to be exposed.

If the end user has the ability to select a location, he should be provided with a list of locations to choose from. Therefore we need one API to list the locations.

Secondly, when the end user picks a location, the current and the forecasted weather should be delivered, therefore the second API we need is the weather API which is bound to a selected location.

Additionally, I thought of letting the developers know the list of supported weather conditions, so that they can easily configure the icons and what not to show on their client applications. Therefore an additional weather condition API too would be implemented.

Considering all of the above, the following are the APIs that I came up with:

  1. GET   /locations - Retrieve list of locations that can be forecasted ordered alphabetically by the city
  2. GET   /conditions - Retrieve list of supported weather conditions
  3. GET /forecasts - Retrieve weather forecast of a city defined by query parameter, responding with an array of 10 forecast items. (Optional "limit" query parameter can define the expected forecast count)
You can use editor.swagger.io to interactively view the full API definition (with the input/output definitions) in yaml is hosted in Github, which is added in the end of this article to avoid clutter.

In order to make things easier, I designed the database to match the API design, which has exactly 3 tables following the ERD (make sure to populate dummy data to view in the app):

That will be all for now, and let's talk about implementing the backend in the next chapter!

The complete API definitions:

You can use editor.swagger.io to interactively view the full API definition (with the input/output definitions) in yaml is hosted in Github.


Next PostNewer Posts Previous PostOlder Posts Home