Let’s enliven the Lambda function we wrote in Part 1. Presently, it returns a fixed data to the caller. Let’s change it to fetch live weather data using OpenWeatherMap’s API service.

Getting Setup with OpenWeatherMap

If you haven’t already, you need to create a free account with OpenWeatherMap. Once you’re in, make your way to the API docs for their current weather service. The first endpoint listed is for current weather data by city name. The endpoint looks like this:

api.openweathermap.org/data/2.5/weather?q={city name},{country code}

So, for Venice, Italy…

api.openweathermap.org/data/2.5/weather?q=venice,it

We need an APPID before we can try out the endpoint. Go here to grab your key.

With a key:

http://api.openweathermap.org/data/2.5/weather?q=venice,it&APPID=80ea139dbc19d7a8a7a01874d540be4b

Calling the endpoint in the browser we get back JSON with a lot of weather related data. The temperature (temp) property is nested within the main object.

...

"main":{  
   "temp":13.24,
   "pressure":1021,
   "humidity":82,
   "temp_min":12,
   "temp_max":15
},
...

The default unit is Kelvins. I’d rather use Celsius (metric) - we can ask for this in our endpoint with the units query parameter:

http://api.openweathermap.org/data/2.5/weather?q=venice,it&APPID=80ea139dbc19d7a8a7a01874d540be4b&units=metric

Deploying with Dependencies

Let’s now move our API endpoint over to the Lambda function. This is where we reached in the (last post)[http://toniando.com/posts/weather-in-venice-web-app-lambda-and-api-gateway/]:

module.exports.getWeather = (event, context, callback) => {
  const data = { temperature: 19.2 };

  const response = {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin" : "*",
    },
    body: JSON.stringify(data)
  };

  callback(null, response);
};

And this is the function updated to fetch weather data from OpenWeatherMap and return the temperature:

const fetch = require('node-fetch');

module.exports.getWeather = (event, context, callback) => {
  const endpoint = 'http://api.openweathermap.org/data/2.5/weather?q=venice,it&APPID=80ea139dbc19d7a8a7a01874d540be4b&units=metric';
  // OpenWeatherMap API endpoint.

  fetch(endpoint)
    .then( res => res.json() )
    .then( body =>  {
      const response = {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin" : "*",
        },
        body: JSON.stringify({ temperature: body.main.temp })
      };

      callback(null, response);
    });
};

Notice we’re importing and using an external module, node-fetch. We could have used node’s http object, but I want to demonstrate how simple it is to deploy Lambda functions with external dependencies with Serverless.

So let’s grab our dependency with yarn (or NPM, if you’d prefer).

pwd
-> /Users/omar/weather-in-venice/service
# Check I'm in the right directory

echo "{}" > package.json
# Create a package.json file, with an empty object "{}"

yarn add node-fetch
# Add the node-fetch package

With the package installed, try running the function locally:

serverless invoke local --function weatherInVenice

You should get a successful response:

{
    "statusCode": 200,
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "body": {
        "temperature": 12
    }
}

Production Ready

We could now deploy our Lambda, but I’m feeling uncomfortable having our API key (APPID) stored within our function. This piece of configuration data is sensitive and is likely to vary between deployments and environments. It would be better if we stored the value in our app’s environment. Environment variables allow us to dynamically pass properties to our code without having to change our code.

AWS Lambda supports environment variables. Let’s see how we can set them with Serverless.

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1
  environment:
    APPID: key_value_goes_here

In our function we can then access APPID via process.env:

module.exports.getWeather = (event, context, callback) => {
  const endpoint = `http://api.openweathermap.org/data/2.5/weather?APPID=${process.env.APPID}&q=venice,it&units=metric`;

We’ve moved our APPID out of our function and dropped it into serverless.yml. Having a static value removes the dynamic nature of environment variables. We’re not able to change the value between deployments and environments without changing our configuration file.

Let’s try something else…

Serverless allows us to reference CLI options in our template, so how about passing APPID from the CLI at the point of deployment?

With Serverless we can reference variables by enclosing them in ${} brackets.

And to reference options passed from the CLI we use the ${opt:my_option} syntax:

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1
  environment:
    APPID: ${opt:appid}

Now, when we deploy, we can pass a value for appid like so:

serverless deploy --appid your_appid

This allows us to decide the APPID at the point of deployment. But it’s a pain to always have to pass your APPID when deploying manually.

A more convenient option is to store our sensitive details in a separate file. Let’s see how we can do that with Serverless.

First, let’s create a new file for storing our secrets:

touch env.yml

atom env.yml

Now add the APPID property:

APPID: 80ea139dbc19d7a8a7a01874d540be4b

With Serverless, to reference variables in other files (JSON or YAML) we use the ${file(./path/to/file.yml)} syntax:

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1
  environment: ${file(./env.yml)}

It’s common to have different configuration data depending on the staging environment. By default, Serverless deploys to dev, but you can change the environment on the command line:

serverless deploy --stage prod
# deploy to prod(uction)

If we wanted to deploy a different APPID depending on the staging environment we could nest our data by environment:

dev:
  APPID: *appid for development*
prod:
  APPID: *appid for production*

With Serverless, we can access nested properties from a file with the ${file(../file.yml):property} syntax:

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1
  environment: ${file(./env.yml):dev}

We now have multiple values of APPID in our env.yml file. But in serverless.yml we’ve fixed it so that we only access the dev version of the variable. We want to change the staging environment at deployment and have that reflected in our serverless.yml file.

Serverless allows us to combine reference properties in a single expression.

The syntax for retrieving the staging environment from a CLI option is ${opt:stage}. We can return a default value if stage isn’t passed, ${opt:stage, 'dev'}. Let’s combine this with the ${file(./file.yml)}** syntax:

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1
  environment: ${file(./env.yml):${opt:stage, 'dev'}}

We’re ready to deploy:

serverless deploy

Opening up the web document we created in the previous post we should continue to see our app working, but now with real data!


The completed code for this app can be found here.

git clone git@github.com:osahyoun/code_examples.git