Skip to main content
Portfolio Context

Artifact type: Tutorial (guided implementation)
Audience: Developers learning to build and deploy a Cloudflare Worker API proxy
Role: Documentation author

Note: This tutorial uses a minimal stub and a public upstream API to demonstrate deployment, configuration, and request validation without requiring a production backend.

Set Up and Verify a Cloudflare Worker API Proxy

Set up a production-style API proxy on Cloudflare Workers, including authentication, KV-backed rate limiting, and request verification — and validate the full request flow locally and in production.

What you’ll do

You will:

  • Initialize a Cloudflare Worker project using Wrangler
  • Apply a working proxy stub
  • Configure a secret and a KV binding
  • Send an authenticated request
  • Verify the response locally and after deployment

Prerequisites

  • A Cloudflare account
  • Node.js installed (for Wrangler CLI)
  • A domain you want to allow in the Worker’s CORS policy
note

This tutorial uses a public upstream API so the request flow can be verified without obtaining an external API key.

In a production proxy, you would typically store an upstream API key as a secret and attach it to outbound requests.

1. Clone the starter repo

Clone the starter project:

Starter repository: https://gitlab.com/ao-ink/cloudflare-worker-api-proxy-tutorial

git clone https://gitlab.com/ao-ink/cloudflare-worker-api-proxy-tutorial.git  
cd cloudflare-worker-api-proxy-tutorial

This repo contains a minimal stub used to verify the deployment and request flow.

2. Initialize the worker project

Create a new Worker project in a subfolder:

npm create cloudflare@latest worker
cd worker

You should now be inside the worker/ directory.

note

This creates a Worker project inside the worker/ directory.
The existing repository files (such as the stub/ folder) are not modified.

Select:

  • Hello World example
  • Worker only
  • JavaScript

This will create a src/index.js file.

Continue through the remaining prompts using the default options.

When asked if you want to deploy, select No.

3. Apply the stub

Replace the contents of src/index.js with the contents of stub/index.stub.js

Configure the allowed origin

Near the top of the stub (~line 3) contains this line:

const ALLOWED_ORIGIN = 'https://example.com'; // must match the Origin header in your request

This value controls which browser requests are allowed to call your Worker.

  • If you have a domain, replace it with your own (for example: https://example.com)
  • If you are just testing locally or using curl, you can leave this as-is

When sending requests, the Origin header must exactly match this value or the request will be rejected.

This stub includes:

  • request validation
  • API key enforcement
  • KV-backed rate limiting
  • upstream proxy logic with a fallback response

It is designed to allow you to run and verify the full flow without writing application logic.

4. Configure a KV namespace

Create a KV namespace

This is commonly used for rate limiting or request tracking.

npx wrangler kv namespace create RATELIMIT

When prompted:

  • “Would you like Wrangler to add it on your behalf?” → select Yes
  • “What binding name would you like to use?” → use the default given
  • “For local dev, do you want to connect to the remote resource instead of a local resource?” → select No

Add KV binding to wrangler.jsonc

Open wrangler.jsonc

Locate this section:

"compatibility_flags": [
"nodejs_compat"
]

Add a comma (,) after the closing bracket (]) of compatibility_flags:

"compatibility_flags": [
"nodejs_compat"
],

Immediately below it, add:

"kv_namespaces": [
{
"binding": "RATELIMIT",
"id": "YOUR_NAMESPACE_ID"
}
]

The result should look like:

  "compatibility_flags": [
"nodejs_compat"
],
"kv_namespaces": [
{
"binding": "RATELIMIT",
"id": "YOUR_NAMESPACE_ID"
}
]

The addition should be placed directly below compatibility_flags and inside the main configuration object. Do not move or remove the closing } at the end of the file.

Replace YOUR_NAMESPACE_ID with the ID returned by Wrangler when you created the KV namespace (for example: 9c4a1b7e3d8f42a6b5c0e91f7a2d6c3e).

If you no longer have the namespace ID, run:

npx wrangler kv namespace list

and copy the ID for your RATELIMIT namespace.

tip

Save your file and redeploy for bindings to take effect.

5. Configure secrets

Create your PROXY_SECRET:

Open another terminal window and generate a random value:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Save this value. You will use it in the next steps.

warning

Save your PROXY_SECRET in a safe place. If you generate a new value, update both the production secret and your .dev.vars file.

Set your production secret:

npx wrangler secret put PROXY_SECRET
tip

Ensure your terminal is in the worker/ directory.

If prompted that the Worker does not exist yet, select Yes to create it and store the secret.

For local development

Create a .dev.vars file inside the worker/ directory:

PROXY_SECRET=your_proxy_secret_here

Replace your_proxy_secret_here with the proxy secret you just generated. No spaces around = or wrap the value in quotes.

Save the file.

warning

.dev.vars is for local development only. Do not commit this file. Verify it is ignored by your .gitignore before committing.

6. Run locally

Start the local Worker:

npx wrangler dev

You should see a local URL like: http://127.0.0.1:8787

7. Send a request

Send an authenticated request. Use curl (macOS/Linux) or PowerShell on Windows:

curl "http://127.0.0.1:8787/verses/john%203:16" \
-H "X-API-Key: YOUR_PROXY_SECRET" \
-H "Origin: https://example.com"

Replace YOUR_PROXY_SECRET with the value you generated in step 5.

Replace https://example.com with the domain configured in index.js as ALLOWED_ORIGIN. The value here must match exactly, or the Worker will reject the request.

The Origin header is required because the Worker enforces CORS. The Origin header must exactly match ALLOWED_ORIGIN, or the Worker will reject the request after API key validation.

PowerShell may display additional response metadata. Using .Content returns only the JSON response.

8. Verify the response

You should receive a JSON response with:

  • requested_reference
  • verse
  • source
  • _tutorial

Example (live upstream)

{
"requested_reference": "john 3:16",
"verse": "For God so loved the world...",
"source": "upstream",
"_tutorial": "Tutorial complete. The Worker fetched a live response from the upstream API."
}

Example (fallback)

{
"requested_reference": "john 3:16",
"verse": "Fallback verse for tutorial verification: John 3:16 — ...",
"source": "fallback",
"_tutorial": "Tutorial complete. The Worker returned a fallback response because the upstream request did not succeed."
}

For readability, you can optionally format the JSON response:

curl (macOS/Linux)

Append at the end of your curl request:

 | python -m json.tool

or, if installed:

 | jq

PowerShell (Windows)

Append at the end of your Invoke-WebRequest command:

 | ConvertFrom-Json | ConvertTo-Json -Depth 5

Fallback responses may also include an _upstream field with details about the upstream failure.

The tutorial is considered complete if you receive a valid JSON response, even if the fallback is used.

The request flow is now working locally. In the next step, you will deploy the same Worker to Cloudflare.

Troubleshooting

If you see:

  • 401 → check X-API-Key
  • 403 → check Origin and ALLOWED_ORIGIN
  • 500 Server misconfig → check .dev.vars, KV binding, and redeploy
  • 429 → wait a minute and retry
  • "source": "fallback" → upstream request did not succeed, but your Worker is working correctly

9. Deploy the Worker

Deploy your Worker:

npx wrangler deploy

Wrangler will return a public URL similar to:

https://your-worker.workers.dev

In your requests, replace the local development URL (http://127.0.0.1:8787) with the public URL.

The request format is the same as before:

curl "https://your-worker.workers.dev/verses/john%203:16" \
-H "X-API-Key: YOUR_PROXY_SECRET" \
-H "Origin: https://example.com"

Ensure the placeholders are replaced with the actual values from earlier steps.

10. Verify the deployed endpoint

The response format should match what you saw during local testing.

You should again receive a JSON response with:

  • requested_reference
  • verse
  • source
  • _tutorial

If source is upstream, the Worker successfully fetched a live response.

If source is fallback, the Worker completed successfully but used the built-in fallback because the upstream request did not succeed.

You now have a publicly accessible API proxy running on Cloudflare Workers with the same behavior you verified locally.

What you built

  • an edge-deployed API proxy running on Cloudflare Workers
  • request validation using a shared secret (X-API-Key)
  • KV-backed rate limiting to control request volume
  • a consistent workflow for local development and deployment

Next steps

  • Replace the example upstream with your own API or service
  • Store any required upstream API keys using wrangler secret put
  • Harden rate limiting, validation, and abuse protections for production use
  • Add caching for frequently requested responses
  • Monitor usage and errors using logs or Cloudflare analytics