I’ve had this weather app running on Heroku for a number of years using the Dark Sky API and I’ve decided to replace it. I decided to replace it for three reasons, the Dark Sky API is shutting down soon, my current app is a little slow and I want to try something new. The weather API is shutting down because they were recently acquired by another company. My current app is slow because the Heroku compute instance ‘goes to sleep’ when inactive and therefore takes a handful of seconds to spin up again, which all things considered is pretty neat but I can do better. Finally, trying something new is a given for me and software professionals in general.

Why serverless? Well I’ve been scratching the surface of the JAM stack and it made me reconsider how I should be making web apps, especially given that most my apps will be lucky if they get 10s of users. Traditionally I thought of all my web apps as client and server and in most cases the server was just there to deal with CORS, serve my app or hide third party API keys. Cloud functions/ Lambdas etc can solve the CORS and third party API key problems and they’re cheaper than a traditional servers, at least for me. Also, right now it looks like the next iteration of my weather app will be a JAM stack site because it’s something I want to give a try. So we will see how it goes but for now I wanted to document and share my new serverless solution using the Open Weather API and Google Cloud Functions

How to make a serverless weather API

  • Register a Open weather account and get an API key
  • Register a google cloud account and login into Google Cloud Platform
  • In GCP make a new project and add a Cloud Function to your projects resource
  • Name the function what you like, for now I am not requiring authorization to run my cloud function, SAVE and click NEXT
  • Then you will see a screen that looks like a text editor with a server.js and package.json

Google cloud functions also support functions written in GoLang, Python, Java and Node.js 8 or 10. By default the node.js environments include express.js

To get started we can make our function return weather for one place, so no matter what parameters we provide we will get weather for that place.

const https = require("https");
exports.getWheather = (req, res) => {
    const key = 'SECRET';
    const options = {
        host: 'api.openweathermap.org',
        path: ''
    };
   options.path = `/data/2.5/weather?lat=50&lon=50&units=metric&appid=${key}`
   https.request(options, (response) => {
        var str = '';
        response.on('data', function (chunk) {
            str += chunk;
        });
        response.on('close', () => {
            res.writeHead(200, { 'Content-Type': "text/html" });
            res.write(str);
            res.end()
        });
    }).end();
};

This is the simplest answer, however we may want to get weather for different locations depending on what the client provides, support different units and do some minimal input validation. For that we can do the following:

var https = require("https");

getUnitsString = (rawUnitsQuery) => {
    if (!rawUnitsQuery || rawUnitsQuery == 'imperial') {
        return 'imperial'
    } else {
        return 'metric'
    }
}

makeWeatherRequest = (clientRes, options) => {
    https.request(options, (response) => {
        var str = '';
        response.on('data', function (chunk) {
            str += chunk;
        });
        response.on('close', () => {
            clientRes.writeHead(200, { 'Content-Type': "text/html" });
            clientRes.write(str);
            clientRes.end()
        });
    }).end();
}

exports.getWheather = (req, res) => {
    const key = 'SECRET';
    const options = {
        host: 'api.openweathermap.org',
        path: ''
    };
    const long = req.query.long;
    const lat = req.query.lat;

    if (!!lat && !!long) {
        const units = getUnitsString(req.query.units)
        options.path = `/data/2.5/weather?lat=${lat}&lon=${long}&units=${units}&appid=${key}`
        makeWeatherRequest(res, options)
    } else {
        res.status(400).send("No lat or long provided")
    }
};

And that is it. Just provide the necessary query parameters like https://us-central1-weather-api-888888.cloudfunctions.net/MY_FUNC/?lat=39&long=-77&units=metric

This leaves room for improvement we would still need to follow one of these auth flows to give us peace of mind around our cloud functions security and go about actually integrating this in a client side app. Either way it’s been a great Saturday and I look forward to using this in a future client side app.