Protecting API keys in frontend apps with Netlify Functions
Build a secure Netlify serverless proxy to hide API keys from client-side code. Includes cross-domain setup and CORS handling.

The problem: API keys in client-side code
I've always been aware of a fundamental security flaw: API keys in client-side code are visible to everyone. Anyone can view your source code or inspect network requests to extract these sensitive credentials.
This creates serious security risks including unauthorized API usage and quota exhaustion. The challenge wasn't discovering this problem but finding a practical solution to mitigate it.
The solution: Serverless functions as API proxies
Since my learning journey focuses primarily on frontend development, I wanted a solution that wouldn't require tedious backend configuration and deployment complexity.
Serverless functions emerged as the perfect alternative. They provide secure proxy functionality without server management.
With serverless, I could focus on what I love most: building frontend applications while still implementing proper security practices.
What we'll build: A simple weather information app
In this tutorial, I'll demonstrate how to get started with Netlify Functions to protect API keys while building a simple weather information page that fetches current weather conditions for a specific city.
Our architecture:
We'll create two separate components that work together across different domains:
- Proxy service: A dedicated Netlify project containing only the serverless function (no frontend) that stores your API key and makes authenticated requests to AccuWeather
- Frontend: A simple HTML page (
index.html
) that calls your serverless function and displays the weather data
How it works:
- The frontend sends a
POST
request with a city name to the serverless function - The function securely calls AccuWeather's API using your protected API key
- Weather data flows back through your function to the frontend for display
- Your API key never appears in client-side code or browser requests
This separation demonstrates a key advantage of serverless functions: one secure API proxy can serve multiple frontend applications across different domains and hosting platforms.
Getting started
Now let's dive into the implementation details, starting with setting up your development environment for your proxy service and creating your first serverless function.
Step 1: Create a new repository from the Netlify template
Use the official Netlify template to create a new repository that includes all the necessary boilerplate:
Template URL: https://github.com/netlify/explorers-up-and-running-with-serverless-functions
Click "Use this template" to create your own repository, then clone it to your local machine using VS Code or your preferred method.
Step 2: Initialize your npm project
Before setting up Netlify, initialize your project as an npm project:
npm init
You can press Enter to accept the default values for all prompts, or customize them as needed.
Step 3: Install the Netlify CLI
Install the Netlify CLI globally to manage your functions and deployments:
npm install netlify-cli --global
Step 4: Set up your Netlify account and project
- Create a Netlify account if you haven't already at
netlify.com
- Login to Netlify from your terminal:
ntl login
- Initialize your project with Netlify:
When prompted, configure these settings:ntl init
- Directory to deploy:
public
- Create
netlify.toml
? Yes
- Directory to deploy:
Step 5: Configure for modern JavaScript (ESM)
Since we'll be using modern JavaScript syntax, add ESM support and configure the build process:
- Add to your
package.json
:{ "type": "module" }
- Update your
netlify.toml
file:[functions] node_bundler = "esbuild"
Step 6: Get your AccuWeather API key
- Sign up for a free account at
developer.accuweather.com
- Create a new app to get your API key
- Add your API key to Netlify:
- Go to your site dashboard on Netlify
- Navigate to Project configuration > Environment variables
- Click Add a single variable
- Set the Key as
ACCUWEATHER_API_KEY
- Check the Contains secret values checkbox (since API keys are sensitive)
- Under the Different value for each deploy context, configure the following:
- Production: Your live API key
- Local development (Netlify CLI): Same API key or a separate development key if you have one
- Click Save variable
Step 7: Create your serverless function
Create the file netlify/functions/weather.js
with the following code:
const ACCUWEATHER_ENDPOINTS = {
CITY_SEARCH: 'http://dataservice.accuweather.com/locations/v1/cities/search',
CURRENT_CONDITIONS: 'http://dataservice.accuweather.com/currentconditions/v1'
};
const ACCUWEATHER_API_KEY = process.env.ACCUWEATHER_API_KEY;
export const handler = async (event) => {
// CORS headers for cross-domain requests
const headers = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // For development - restrict in production
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "POST, OPTIONS"
};
// Handle OPTIONS preflight request (required for CORS)
if (event.httpMethod === "OPTIONS") {
return {
statusCode: 200,
headers,
body: ""
};
}
// Handle POST request
if (event.httpMethod === "POST") {
try {
const { city } = JSON.parse(event.body);
// Get location key
const locationResponse = await fetch(`${ACCUWEATHER_ENDPOINTS.CITY_SEARCH}?apikey=${ACCUWEATHER_API_KEY}&q=${encodeURIComponent(city)}`);
const locationData = await locationResponse.json();
const { Key: locationKey } = locationData[0];
// Get current conditions
const weatherResponse = await fetch(`${ACCUWEATHER_ENDPOINTS.CURRENT_CONDITIONS}/${locationKey}?apikey=${ACCUWEATHER_API_KEY}`);
const weatherData = await weatherResponse.json();
const { WeatherText: weatherText } = weatherData[0];
return {
statusCode: 200,
headers,
body: JSON.stringify({
message: `The current weather in ${city} is: ${weatherText}`
}),
};
} catch (error) {
return {
statusCode: 500,
headers,
body: JSON.stringify({
error: "Failed to fetch weather data"
})
};
}
}
// Handle unsupported methods
return {
statusCode: 405,
headers,
body: JSON.stringify({ error: "Method Not Allowed" })
};
}
Key points about this function:
- The API key is stored securely in
process.env.ACCUWEATHER_API_KEY
- The function handles two API calls: first to get the location key, then to get weather data
- It returns a simple message with the weather information
Key additions for cross-domain support:
- CORS headers: Allow requests from any origin (*) for development
- OPTIONS handler: Responds to browser preflight requests
- Method validation: Ensures only POST and OPTIONS are accepted
For production, replace "Access-Control-Allow-Origin": "*" with your specific frontend domain:
"Access-Control-Allow-Origin": "https://yourfrontend.com"
Step 8: Deploy your proxy service
Let's deploy the proxy service to production:
- Add all your changes to git:
git add .
- Commit your changes with a descriptive message:
git commit -m "feat: add weather proxy function with CORS support"
- Push to your main branch:
git push origin main
This will automatically trigger a build inside Netlify since your repository is connected to your Netlify site.
Important reminder: Make sure your ACCUWEATHER_API_KEY
environment variable is properly set in your Netlify site settings before deployment (as covered in Step 6), as the serverless function won't work without it.
Step 9: Create your frontend
Now we'll create a simple HTML page separate from your proxy service that will call your deployed Netlify function. This frontend can be hosted anywhere (local file, CodeSandbox, different hosting provider, etc.).
Create an index.html
file with the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Weather App - Secure API Proxy Demo</title>
</head>
<body>
<h1>Weather App - Secure API Proxy Demo</h1>
<button id="fetch-btn">Fetch Weather For Tokyo</button>
<p id="response-output">Response Example Placeholder</p>
<script>
// Replace 'YOUR_NETLIFY_SITE' with your actual Netlify site name
const NETLIFY_FUNCTION_URL = 'https://YOUR_NETLIFY_SITE.netlify.app/.netlify/functions/weather';
document.addEventListener('DOMContentLoaded', () => {
const fetchBtn = document.getElementById('fetch-btn')
const responseText = document.getElementById('response-output')
fetchBtn.addEventListener('click', async () => {
const response = await fetch(`${NETLIFY_FUNCTION_URL}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ city: "Tokyo" }),
});
const data = await response.json();
responseText.innerText = data.message || 'No message received';
});
})
</script>
</body>
</html>
Important: Replace YOUR_NETLIFY_SITE
in the NETLIFY_FUNCTION_URL
with your actual Netlify site name.
Step 10: Test your cross-domain setup
- Open your frontend: Open index.html in your browser
- Test the connection: Click the "Fetch Weather For Tokyo" button
- Verify the results: You should see the placeholder text "Response Example Placeholder" replaced with actual weather information like: "The current weather in Tokyo is: Sunny"
Step 11: Optional security enhancement (rate limiting)
While hiding your API key is the primary goal of this tutorial, you can add another layer of protection by enabling Netlify's built-in rate limiting. This will protect your serverless functions from abuse.
You can configure rate limiting directly in your netlify.toml
file:
[[rules]]
for = "/.netlify/functions/*"
[rules.rate_limit]
rate = "20 requests per minute"
action = "block"
What we accomplished
This setup demonstrates the core security principle: your AccuWeather API key is completely hidden from client-side code. When users inspect your website's source code or network requests, they'll only see calls to your serverless function, not to the AccuWeather API directly.
Key benefits achieved:
- API key protection: Sensitive credentials remain secure on Netlify's servers
- Cross-domain flexibility: Your proxy can serve multiple frontend applications across different hosting platforms
- Simple architecture: No complex backend infrastructure required
The serverless function acts as a secure proxy, making the actual API calls with your protected credentials and returning only the data you want to share with the frontend.
- Share this page