Protecting API keys with Serverless Functions
Tags: react, serverless, aws, javascript
Published: 2020-04-26 12:19:21

Imagine you need to develop the front-end of an APP that needs to get  a list of the most popular movies from the [MovieDB](https://developers.themoviedb.org/) API.

## Let's do it !

Go to [MovieDB](https://developers.themoviedb.org/) and signup to get your own API Key and follow along.

We will create a new project named **protectingapisecrets** using create-react-app and start coding our front-end

```jsx
npx create-react-app protectingapisecrets
cd protectingapisecrets
touch .env
npm install axios
```

Open this project with your favourite Code Editor, edit your **.env** file and add a variable with you API Key 

```jsx
// .env

REACT_APP_API_KEY=<<your api key>>
```

next open your **.gitignore** file and add a line with your **.env** file and finally delete all files inside your **src** folder and create a clean i**ndex.js** **App.js** and **App.css**

## Start coding

```jsx
// index.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```

```jsx
// App.js

import React, { useState, useEffect } from "react"
import axios from "axios"
import "./App.css"

const App = () => {
  const [movies, setMovies] = useState(null)
    
	async function fetchMovies() {
	    const url = `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.REACT_APP_API_KEY}&language=en-US&page=1`
      const response = await axios.get(url)
	    const data = response.data.results
	    setMovies(data)
	  }  

	useEffect(() => {
    fetchMovies()
  }, [])

	return (
    <>
      {movies === null ? (
        <div className="loading">
          <h2>Loading ...</h2>
        </div>
      ) : (
        <>
          <div className="container">
            {movies.map((movie) => (
              <div className="movie" key={movie.id}>
                <img src={`https://image.tmdb.org/t/p/w185/${movie.poster_path}`} alt={movie.title} />
              </div>
            ))}
          </div>
        </>
      )}
    </>
   )
  }

export default App
```

```css
// App.css

*,
*::after,
*::before {
  margin: 0rem;
  padding: 0rem;
  box-sizing: inherit;
}

html {
  font-size: 62.5%;
  scroll-behavior: smooth;
}

body {
  box-sizing: border-box;
  background-color: #222831;
}

.loading {
  padding-top: 5rem;
  text-align: center;
}

.loading h2 {
  color: white;
  font-size: 2rem;
}

.container {
  margin: auto;
  padding: 2rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  max-width: 110rem;
  grid-gap: 2rem;
}

.movie img {
  width: 100%;
}
```

Cool, now let's run 

```jsx
npm start 
```

and check if everything is behaving like we expected

![Image 1](https://i.postimg.cc/L69TZkbn/1.jpg)

## Deploying

Amazing!

We completed our front-end  now it's time to deploy it .

We will do this really easily and in just tree steps with Netlify:  

1st: Create a new GitHub repository and push your code.

2nd: Create and account on Netlify and connect you account to your GitHub.

3th: On your Netlify panel select "New Site from git" and chose the repository you created, you also need to check "show advanced" and add a new variable like this :

![Image 2](https://i.postimg.cc/d3q8rvnz/2.jpg)

click "Deploy Site" and that's it, we now have a live version of our app ! 

## The Problem

We stored our API Key in an environment variable to prevent it from being available on our code but if anyone opens the chrome dev tools while browsing your site, quickly can find your key.

![Image 3](https://i.postimg.cc/ydhmDWhm/3.jpg)

soo , what can we do to protect our API key ?

## Serverless Functions

We can make a serverless function that handles our API call for us so we don't have to public expose our key.

Let's try it out, go back to your terminal and run:

```jsx
npm install netlify-lambda http-proxy-middleware env-cmd
mkdir functions
touch netlify.toml
```

Update scripts in your **package.json** file to look like this:

```jsx
// package.json

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
		"lambda-serve": "env-cmd netlify-lambda serve functions",
		"lambda-build": "netlify-lambda build functions"
  },
```

add this lines to **netlify.toml** file and add the functions folder to your **.gitignorefile**

```jsx
// netlify.toml

[build]
	functions = "lambda"
```

add a file named **setupProxy.js** to your **src** folder and past this code:

```jsx
// setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/.netlify/functions/',
    createProxyMiddleware({
      target: 'http://localhost:9000',
      "pathRewrite": {
        "^/\\.netlify/functions": ""
      }
    })
  );
};
```

This proxy setup will allow you to ping different endpoints depending on witch environment you are, if you are in development you want to ping **localhost** and in production you want the **./netlify/functions** endpoint.

## Coding our function

Let's create a file named **getMovies.js** inside our **functions** directory

```jsx
// getMovies.js

const axios = require("axios")

exports.handler = function(event, context, callback) {
	const { API_KEY } = process.env

	const url = `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}&language=en-US&page=1`

	const send = body => {
		callback(null, {
			statusCode: 200,
			body: JSON.stringify(body)
		})
	}

	const getMovies = async () => {
		const response = await axios.get(url)
		const data = response.data.results
		
		send(data)
	}

	getMovies()
}
```

Now we need to edit our **fetchMovies** function inside **App.js** to use the serveless function instead of pinging the moviedb api directly:

```jsx
async function fetchMovies() {
    const url = `/.netlify/functions/getMovies`
    
    const response = await axios.get(url)
    const data = response.data
    setMovies(data)
  }
```

And finally edit the **.env** file and change the name of the variable from **REACT_APP_API_KEY** to **API_KEY**

## Great, let's test it out !

Open two terminal windows and run **npm start** on the first and **npm run lambda-serve** on the second one and check your network tab

![Image 4](https://i.postimg.cc/Y9BN2QF2/4.jpg)

Cool, we are calling the serverless function hiding the real endpoint of the api, let's deploy it to Netlify, open your terminal and run:

```jsx
git add .
git commit -m "finished version"
git push
```

When you push a commit to your GitHub repo Netlify will trigger a new deploy for your site. You just need to do one extra step and your are done, open your Netlify Panel and change the name of the environment variable you created on your fist deploy from **REACT_APP_API_KEY** to **API_KEY**

![Image 5](https://i.postimg.cc/cJnMNFr5/5.jpg)

We are done, see you soon !

You can check my GitHub  repo here: [https://github.com/NunoCPNP/protectapisecrets](https://github.com/NunoCPNP/protectapisecrets)

and my deployed version here: [https://protectingapisecrets.netlify.app/](https://protectingapisecrets.netlify.app/)