Embedding API keys in your network requests exposes the resource access credentials in plain text if any one opens up the network tab in browser dev tools. This can be dangerous especially if the API key has a financial cost attached to it.

One way around this problem is to create a proxy server that stores the API key and exposes an endpoint for your client to consume in order to access a given resource. To achieve this, we use URL params to attach the API key to our route and then the client can provide any other query parameters if needed without having to specify the API key. Let’s use an example to bring this point home.

This example was inspired by Brad Traversy’s video on this concept.

Our example will consist of two applications:

  1. A backend application that acts as a proxy server (Part I)
  2. A react application that will consume the backend api endpoints (Part II)

This article will focus on Part I which is building the backend application.

Prerequisites
  • Basic understanding of NodeJS
  • Basic understanding of express
  • Openweather API key

Our application will be a basic weather app that uses the openweather API key to retrieve weather information for different cities. If you don’t already have one, please get an openweather API key by creating a free account.

Set up working directory

In your directory of choice, create a new folder that we’ll be working in for this project.

mkdir ow-proxy-server
# create a src directory within our new folder
mkdir src
# create app.js file within the src directory
touch src/app.js
# create .env file
touch .env
Initialize npm and install dependencies

Inside our new directory:

# 1. initialize npm
npm init -y
# 2. install dependencies
npm i express cors dotenv needle
# 3. install dev dependencies
npm i -D nodemon
Edit package.json file
// edit the scripts object by adding a 
// start and dev command
{
  "start": "node src/app.js",
  "dev": "nodemon src/app.js"
}
Set up the app.js file
// import statements
const express = require('express')
const dotenv = require('dotenv');
const cors = require('cors');

// initialize environment variables
dotenv.config();

const PORT = process.env.PORT || 7000;

const app = express();

// disable fingerprinting
app.disable('x-powered-by');
// enable proxy server
app.set('trust proxy', 1);

// enable cors
app.use(cors());

// set up routes
// we will create our routes folder in the next step
app.use('/api', require('./routes'))

// start server
app.listen(PORT, () => console.log(`app is listening on port ${PORT}`))

Sofar, we’ve set up our server to listen on the port defined in our .env file or port 7000 in case the port environment variable isn’t defined. Next, we’re setting the ‘trust proxy’ to 1 because the application will be running as a proxy server.

If you run npm run dev, the application should start and listen on the port you’ve defined.

Create a route to interact with openweather API

First, create a routes folder inside /src directory. Next create a javascript file that will house our route handler.

# create routes directory
mkdir src/routes
# create javascript file for our route
touch src/routes/index.js
Setup route

Before we setup our route, let’s populate our .env file with some environment variables;

API_BASE_URL="https://api.openweathermap.org/data/2.5/weather"
API_KEY_NAME="appid"
API_KEY_VALUE="" #insert api key from openweather app

Inside src/routes/index.js:

const url = require('url');
const express = require('express');
const router = express.Router();
// needle is an http client, similar to node-fetch and axios
const needle = require('needle');

// environment variables
const API_BASE_URL = process.env.API_BASE_URL;
// the variable below houses the name of the query parameter
// which holds the api key
const API_KEY_NAME = process.env.API_KEY_NAME;
const API_KEY_VALUE = process.env.API_KEY_VALUE;

router.get('/', async(req, res) => {
  try{
    const params = new URLSearchParams({
      [API_KEY_NAME]: API_KEY_VALUE,
      // our client will pass city param so we handle that below
      // this will grab any query parameter our client passes us
      ...url.parse(req.url, true).query
    })
    const response = needle('get', `${API_BASE_URL}?${params}`);
    const data = response.body;

    // when in development environment, log request url
    if(process.env.NODE !== 'production'){
      console.log(`REQUEST: ${API_BASE_URL}?${params}`)
    }
    res.status(200).json(data);
  } catch(err){
    res.status(500).json({'error': true, 'message': 'Server Error'});
  }
})

module.exports = router;

The file above is enough to start interacting with the openweather api via our proxy server. To test it, make sure the server is up by running npm run dev then open up postman or any other rest client. I’ll use curl to test the endpoint.

curl http://localhost:7000/api

This won’t give us any data. That’s because we haven’t really defined what city we would like to query for weather data. For now we get back the following output:

{
	"cod": "400",
	"message": "Nothing to geocode"
}

To get back some useful data, we’ll need to append a query parameter with a name of a city of interest. I’ll test using Kigali. You can refer to openweather website to learn more about the query parameter it expects for cities. The q parameter is required when querying by city name.

curl http://localhost:7000/api?q=kigali

Now we should be able to get some data.

{
	"coord": {
		"lon": 30.0588,
		"lat": -1.95
	},
	"weather": [
		{
			"id": 802,
			"main": "Clouds",
			"description": "scattered clouds",
			"icon": "03n"
		}
	],
	"base": "stations",
	"main": {
		"temp": 294.11,
		"feels_like": 293.46,
		"temp_min": 294.11,
		"temp_max": 294.86,
		"pressure": 1020,
		"humidity": 46
	},
	"visibility": 10000,
	"wind": {
		"speed": 1.54,
		"deg": 170
	},
	"clouds": {
		"all": 40
	},
	"dt": 1658782494,
	"sys": {
		"type": 2,
		"id": 47683,
		"country": "RW",
		"sunrise": 1658721923,
		"sunset": 1658765212
	},
	"timezone": 7200,
	"id": 202061,
	"name": "Kigali",
	"cod": 200
}

In this article, we have created an Express backend application that serves as a proxy server for client applications to consume a third party API such as OpenWeatherMap API. We have seen how using a proxy server can have security benefits when it comes to hiding API keys from the client browser.

In Part II, we’ll create a client application with a user interface that can interact with our proxy server.

Thank you for following along and stay tuned for Part II.

Link to github reposity: https://github.com/mutsinzi/ow-express-proxy