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
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.
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.
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.
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
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.
.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
- PowerShell
curl "http://127.0.0.1:8787/verses/john%203:16" \
-H "X-API-Key: YOUR_PROXY_SECRET" \
-H "Origin: https://example.com"
(Invoke-WebRequest "http://127.0.0.1:8787/verses/john%203:16" -UseBasicParsing -Headers @{
"X-API-Key" = "YOUR_PROXY_SECRET"
"Origin" = "https://example.com"
}).Content
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_referenceversesource_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→ checkX-API-Key403→ checkOriginandALLOWED_ORIGIN500 Server misconfig→ check.dev.vars, KV binding, and redeploy429→ 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
- PowerShell
curl "https://your-worker.workers.dev/verses/john%203:16" \
-H "X-API-Key: YOUR_PROXY_SECRET" \
-H "Origin: https://example.com"
(Invoke-WebRequest "https://your-worker.workers.dev/verses/john%203:16" -UseBasicParsing -Headers @{
"X-API-Key" = "YOUR_PROXY_SECRET"
"Origin" = "https://example.com"
}).Content
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_referenceversesource_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