Skip to content

Commit 038b7d8

Browse files
harshil1712hyperlint-ai[bot]kodster28
authored andcommitted
[Queues] Add rate limit tutorial (#16797)
* add rate limit tutorial * Update src/content/docs/queues/tutorials/handle-rate-limits/index.mdx Co-authored-by: hyperlint-ai[bot] <154288675+hyperlint-ai[bot]@users.noreply.github.com> * [Hyperlint] Update checks to be more forgiving for Wrangler capitalization + add new check + remove logic around backticks * Revert "[Hyperlint] Update checks to be more forgiving for Wrangler capitalization + add new check + remove logic around backticks" This reverts commit 645183c. * update queues resend tutorial * update repo link and minor changes * minor fixes * update Render componenet params * update the category --------- Co-authored-by: hyperlint-ai[bot] <154288675+hyperlint-ai[bot]@users.noreply.github.com> Co-authored-by: kodster28 <[email protected]>
1 parent 5601bdb commit 038b7d8

File tree

1 file changed

+389
-0
lines changed
  • src/content/docs/queues/tutorials/handle-rate-limits

1 file changed

+389
-0
lines changed
Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
---
2+
updated: 2024-09-25
3+
difficulty: Beginner
4+
title: Handle rate limits of external APIs
5+
summary: Example of how to use Queues to handle rate limits of external APIs.
6+
content_type: 📝 Tutorial
7+
pcx_content_type: tutorial
8+
products:
9+
- Workers
10+
- Queues
11+
languages:
12+
- TypeScript
13+
sidebar:
14+
order: 1002
15+
head:
16+
- tag: title
17+
content: Cloudflare Queues - Queues & Rate Limits
18+
description: Example of how to use Queues to handle rate limits of external APIs.
19+
---
20+
21+
import { Render, PackageManagers } from "~/components";
22+
23+
This tutorial explains how to use Queues to handle rate limits of external APIs by building an application that sends email notifications using [Resend](https://www.resend.com/). However, you can use this pattern to handle rate limits of any external API.
24+
25+
Resend is a service that allows you to send emails from your application via an API. Resend has a default [rate limit](https://resend.com/docs/api-reference/introduction#rate-limit) of two requests per second. You will use Queues to handle the rate limit of Resend.
26+
27+
## Prerequisites
28+
29+
<Render file="prereqs" product="workers" />
30+
31+
4. Sign up for [Resend](https://resend.com/) and generate an API key by following the guide on the [Resend documentation](https://resend.com/docs/dashboard/api-keys/introduction).
32+
33+
5. Additionally, you will need access to Cloudflare Queues.
34+
35+
<Render file="enable-queues" />
36+
37+
## 1. Create a new Workers application
38+
39+
To get started, create a Worker application using the [`create-cloudflare` CLI](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare). Open a terminal window and run the following command:
40+
41+
<PackageManagers
42+
type="create"
43+
pkg="cloudflare@latest"
44+
args={"resend-rate-limit-queue"}
45+
/>
46+
47+
<Render
48+
file="c3-post-run-steps"
49+
product="workers"
50+
params={{
51+
category: "hello-world",
52+
type: "Hello World Worker",
53+
lang: "TypeScript",
54+
}}
55+
/>
56+
57+
Then, go to your newly created directory:
58+
59+
```sh frame="none"
60+
cd resend-rate-limit-queue
61+
```
62+
63+
## 2. Set up a Queue
64+
65+
You need to create a Queue and a binding to your Worker. Run the following command to create a Queue named `rate-limit-queue`:
66+
67+
```sh title="Create a Queue"
68+
npx wrangler queues create rate-limit-queue
69+
```
70+
71+
```sh output
72+
Creating queue rate-limit-queue.
73+
Created queue rate-limit-queue.
74+
```
75+
76+
### Add Queue bindings to `wrangler.toml`
77+
78+
In your `wrangler.toml` file, add the following:
79+
80+
```toml
81+
[[queues.producers]]
82+
binding = "EMAIL_QUEUE"
83+
queue = "rate-limit-queue"
84+
85+
[[queues.consumers]]
86+
queue = "rate-limit-queue"
87+
max_batch_size = 2
88+
max_batch_timeout = 10
89+
max_retries = 3
90+
```
91+
92+
It is important to include the `max_batch_size` of two to the consumer queue is important because the Resend API has a default rate limit of two requests per second. This batch size allows the queue to process the message in the batch size of two. If the batch size is less than two, the queue will wait for 10 seconds to collect the next message. If no more messages are available, the queue will process the message in the batch. For more information, refer to the [Batching, Retries and Delays documentation](/queues/configuration/batching-retries)
93+
94+
Your final `wrangler.toml` file should look similar to the example below.
95+
96+
```toml title="wrangler.toml"
97+
#:schema node_modules/wrangler/config-schema.json
98+
name = "resend-rate-limit-queue"
99+
main = "src/index.ts"
100+
compatibility_date = "2024-09-09"
101+
compatibility_flags = ["nodejs_compat"]
102+
103+
[[queues.producers]]
104+
binding = "EMAIL_QUEUE"
105+
queue = "rate-limit-queue"
106+
107+
[[queues.consumers]]
108+
queue = "rate-limit-queue"
109+
max_batch_size = 2
110+
max_batch_timeout = 10
111+
max_retries = 3
112+
```
113+
114+
## 3. Add bindings to environment
115+
116+
Add the bindings to the environment interface in `worker-configuration.d.ts`, so TypeScript correctly types the bindings. Type the queue as `Queue<any>`. Refer to the following step for instructions on how to change this type.
117+
118+
```ts title="worker-configuration.d.ts"
119+
interface Env {
120+
EMAIL_QUEUE: Queue<any>;
121+
}
122+
```
123+
124+
## 4. Send message to the queue
125+
126+
The application will send a message to the queue when the Worker receives a request. For simplicity, you will send the email address as a message to the queue. A new message will be sent to the queue with a delay of one second.
127+
128+
```ts title="src/index.ts"
129+
export default {
130+
async fetch(req: Request, env: Env): Promise<Response> {
131+
try {
132+
await env.EMAIL_QUEUE.send(
133+
{ email: await req.text() },
134+
{ delaySeconds: 1 },
135+
);
136+
return new Response("Success!");
137+
} catch (e) {
138+
return new Response("Error!", { status: 500 });
139+
}
140+
},
141+
};
142+
```
143+
144+
This will accept requests to any subpath and forwards the request's body. It expects that the request body to contain only an email. In production, you should check that the request was a `POST` request. You should also avoid sending such sensitive information (email) directly to the queue. Instead, you can send a message to the queue that contains a unique identifier for the user. Then, your consumer queue can use the unique identifier to look up the email address in a database and use that to send the email.
145+
146+
## 5. Process the messages in the queue
147+
148+
After the message is sent to the queue, it will be processed by the consumer Worker. The consumer Worker will process the message and send the email.
149+
150+
Since you have not configured Resend yet, you will log the message to the console. After you configure Resend, you will use it to send the email.
151+
152+
Add the `queue()` handler as shown below:
153+
154+
```ts title="src/index.ts" ins={1-3,17-28}
155+
interface Message {
156+
email: string;
157+
}
158+
159+
export default {
160+
async fetch(req: Request, env: Env): Promise<Response> {
161+
try {
162+
await env.EMAIL_QUEUE.send(
163+
{ email: await req.text() },
164+
{ delaySeconds: 1 },
165+
);
166+
return new Response("Success!");
167+
} catch (e) {
168+
return new Response("Error!", { status: 500 });
169+
}
170+
},
171+
async queue(batch: MessageBatch<Message>, env: Env): Promise<void> {
172+
for (const message of batch.messages) {
173+
try {
174+
console.log(message.body.email);
175+
// After configuring Resend, you can send email
176+
message.ack();
177+
} catch (e) {
178+
console.error(e);
179+
message.retry({ delaySeconds: 5 });
180+
}
181+
}
182+
},
183+
};
184+
```
185+
186+
The above `queue()` handler will log the email address to the console and send the email. It will also retry the message if sending the email fails. The `delaySeconds` is set to five seconds to avoid sending the email too quickly.
187+
188+
To test the application, run the following command:
189+
190+
```sh title="Start the development server"
191+
npm run dev
192+
```
193+
194+
Use the following cURL command to send a request to the application:
195+
196+
```sh title="Test with a cURL request"
197+
curl -X POST -d "[email protected]" http://localhost:8787/
198+
```
199+
200+
```sh output
201+
[wrangler:inf] POST / 200 OK (2ms)
202+
QueueMessage {
203+
attempts: 1,
204+
body: { email: '[email protected]' },
205+
timestamp: 2024-09-12T13:48:07.236Z,
206+
id: '72a25ff18dd441f5acb6086b9ce87c8c'
207+
}
208+
```
209+
210+
## 6. Set up Resend
211+
212+
To call the Resend API, you need to configure the Resend API key. Create a `.dev.vars` file in the root of your project and add the following:
213+
214+
```txt title=".dev.vars"
215+
RESEND_API_KEY='your-resend-api-key'
216+
```
217+
218+
Replace `your-resend-api-key` with your actual Resend API key.
219+
220+
Next, update the `Env` interface in `worker-configuration.d.ts` to include the `RESEND_API_KEY` variable.
221+
222+
```ts title="worker-configuration.d.ts" ins={3}
223+
interface Env {
224+
EMAIL_QUEUE: Queue<any>;
225+
RESEND_API_KEY: string;
226+
}
227+
```
228+
229+
Lastly, install the [`resend` package](https://www.npmjs.com/package/resend) using the following command:
230+
231+
```sh title="Install Resend"
232+
npm install resend
233+
```
234+
235+
You can now use the `RESEND_API_KEY` variable in your code.
236+
237+
## 7. Send email with Resend
238+
239+
In your `src/index.ts` file, import the Resend package and update the `queue()` handler to send the email.
240+
241+
```ts title="src/index.ts" ins={1,21,26-40} del={24,41}
242+
import { Resend } from "resend";
243+
244+
interface Message {
245+
email: string;
246+
}
247+
248+
export default {
249+
async fetch(req: Request, env: Env): Promise<Response> {
250+
try {
251+
await env.EMAIL_QUEUE.send(
252+
{ email: await req.text() },
253+
{ delaySeconds: 1 },
254+
);
255+
return new Response("Success!");
256+
} catch (e) {
257+
return new Response("Error!", { status: 500 });
258+
}
259+
},
260+
async queue(batch: MessageBatch<Message>, env: Env): Promise<void> {
261+
// Initialize Resend
262+
const resend = new Resend(env.RESEND_API_KEY);
263+
for (const message of batch.messages) {
264+
try {
265+
console.log(message.body.email);
266+
// send email
267+
const sendEmail = await resend.emails.send({
268+
269+
to: [message.body.email],
270+
subject: "Hello World",
271+
html: "<strong>Sending an email from Worker!</strong>",
272+
});
273+
274+
// check if the email failed
275+
if (sendEmail.error) {
276+
console.error(sendEmail.error);
277+
message.retry({ delaySeconds: 5 });
278+
} else {
279+
// if success, ack the message
280+
message.ack();
281+
}
282+
message.ack();
283+
} catch (e) {
284+
console.error(e);
285+
message.retry({ delaySeconds: 5 });
286+
}
287+
}
288+
},
289+
};
290+
```
291+
292+
The `queue()` handler will now send the email using the Resend API. It also checks if sending the email failed and will retry the message.
293+
294+
The final script is included below:
295+
296+
```ts title="src/index.ts"
297+
import { Resend } from "resend";
298+
299+
interface Message {
300+
email: string;
301+
}
302+
303+
export default {
304+
async fetch(req: Request, env: Env): Promise<Response> {
305+
try {
306+
await env.EMAIL_QUEUE.send(
307+
{ email: await req.text() },
308+
{ delaySeconds: 1 },
309+
);
310+
return new Response("Success!");
311+
} catch (e) {
312+
return new Response("Error!", { status: 500 });
313+
}
314+
},
315+
async queue(batch: MessageBatch<Message>, env: Env): Promise<void> {
316+
// Initialize Resend
317+
const resend = new Resend(env.RESEND_API_KEY);
318+
for (const message of batch.messages) {
319+
try {
320+
// send email
321+
const sendEmail = await resend.emails.send({
322+
323+
to: [message.body.email],
324+
subject: "Hello World",
325+
html: "<strong>Sending an email from Worker!</strong>",
326+
});
327+
328+
// check if the email failed
329+
if (sendEmail.error) {
330+
console.error(sendEmail.error);
331+
message.retry({ delaySeconds: 5 });
332+
} else {
333+
// if success, ack the message
334+
message.ack();
335+
}
336+
} catch (e) {
337+
console.error(e);
338+
message.retry({ delaySeconds: 5 });
339+
}
340+
}
341+
},
342+
};
343+
```
344+
345+
To test the application, start the development server using the following command:
346+
347+
```sh title="Start the development server"
348+
npm run dev
349+
```
350+
351+
Use the following cURL command to send a request to the application:
352+
353+
```sh title="Test with a cURL request"
354+
curl -X POST -d "[email protected]" http://localhost:8787/
355+
```
356+
357+
On the Resend dashboard, you should see that the email was sent to the provided email address.
358+
359+
## 8. Deploy your Worker
360+
361+
To deploy your Worker, run the following command:
362+
363+
```sh title="Deploy your Worker"
364+
npx wrangler deploy
365+
```
366+
367+
Lastly, add the Resend API key using the following command:
368+
369+
```sh title="Add the Resend API key"
370+
npx wrangler secret put RESEND_API_KEY
371+
```
372+
373+
Enter the value of your API key. Your API key will get added to your project. You can now use the `RESEND_API_KEY` variable in your code.
374+
375+
You have successfully created a Worker which can send emails using the Resend API respecting rate limits.
376+
377+
To test your Worker, you could use the following cURL request. Replace `<YOUR_WORKER_URL>` with the URL of your deployed Worker.
378+
379+
```bash title="Test with a cURL request"
380+
curl -X POST -d "[email protected]" <YOUR_WORKER_URL>
381+
```
382+
383+
Refer to the [GitHub repository](https://github.com/harshil1712/queues-rate-limit) for the complete code for this tutorial. If you are using [Hono](https://hono.dev/), you can refer to the [Hono example](https://github.com/harshil1712/resend-rate-limit-demo).
384+
385+
## Related resources
386+
387+
- [How Queues works](/queues/reference/how-queues-works/)
388+
- [Queues Batching and Retries](/queues/configuration/batching-retries/)
389+
- [Resend](https://resend.com/docs/)

0 commit comments

Comments
 (0)