Building Background Jobs with Taskless

In this guide, we're going to build a simple background job processor in Taskless. While we'll be creating placeholders for a variety of functions (using console.log), it's a great example of how you can use Taskless to move work out of the blocking path.

Password Resets, but Faster and More Reliable

We're going to be fixing up a very simple password reset flow for a hypothetical website. A completed example is available in the examples folder if you'd like to just look at the final product.

In this example, we're using a third party service for sending email, emulated through some setTimeout calls. Like any other service, there can be hiccups, downtime, and slow connections. Because we don't want our users waiting while we talk to the email service, we're going to shift this work out of the API request the user makes to request the reset. This process, doing work in the background is perfect for Taskless.

Environment Setup

We'll start by creating a copy of our next-guide example app. It was created using create-next-app with @taskless/next, @taskless/dev, and npm-run-all added. We've also modified the scripts section to launch Taskless alongside our Next app.

Note: You can also use npm or yarn for dependency management.

1npx create-next-app@latest next-background --use-pnpm --example "https://github.com/taskless/examples/next-guide"
2cd next-guide
3touch .env.local

That's it for setup. When we run pnpm dev from our console, we'll get the familiar next.js starter page. Next.js will run on port 3000, while the Taskless Dev Server runs on 3001,

next13-home

The Password Reset Page

To keep things simple, we'll borrow several of the Next.js starter styles and add a basic input and button for password resetting. In a production app, you'd likely use react-hook-form, Formik, or another form management library. The file we're looking at is /pages/auth/reset.tsx:

1import Head from "next/head";
2import { Inter } from "@next/font/google";
3import home from "@/styles/Home.module.css";
4import styles from "@/styles/PWReset.module.css";
5import { useCallback, useRef, useState } from "react";
6
7const inter = Inter({ subsets: ["latin"] });
8
9export default function Home() {
10 const [processing, setProcessing] = useState(false);
11 const inputRef = useRef<HTMLInputElement>(null);
12 const handleSubmit = useCallback(async () => {
13 if (processing || !inputRef.current) {
14 return;
15 }
16 setProcessing(true);
17
18 setProcessing(true);
19 try {
20 const resp = await fetch("/api/auth/reset-slow", {
21 method: "POST",
22 body: JSON.stringify({
23 email: inputRef.current.value,
24 }),
25 });
26 const res = await resp.json();
27 console.log(res);
28 } catch (e) {
29 console.error(e);
30 } finally {
31 setProcessing(false);
32 }
33
34 setProcessing(false);
35 }, [processing]);
36
37 return (
38 <>
39 <Head>
40 <title>Password Reset Example - Taskless</title>
41 <meta name="description" content="Generated by create next app" />
42 <meta name="viewport" content="width=device-width, initial-scale=1" />
43 <link rel="icon" href="/favicon.ico" />
44 </Head>
45 <main className={home.main}>
46 <div className={home.center}>
47 <div className={styles.grid}>
48 <h1 className={inter.className}>Reset Your Password</h1>
49 <div
50 className={styles.grid}
51 style={{ opacity: processing ? 0.5 : 1 }}
52 >
53 <input
54 className={styles.input}
55 type="email"
56 placeholder="your email"
57 ref={inputRef}
58 readOnly={processing}
59 />
60 <button type="button" onClick={handleSubmit}>
61 Forgot Password
62 </button>
63 </div>
64 </div>
65 </div>
66 </main>
67 </>
68 );
69}

As you interact with the page at http://localhost:3000/auth/reset, you'll notice that the page "hangs" for a moment or two. Looking in our console, we can see our Mail Service is occasioanly timing out. And while we're waiting on our Mail Service to respond, the user's stuck waiting with us. 😬

1[dev:next] ⤵️ Making request via reset-slow
2[dev:next] [⏳] Check abuse rate limits
3[dev:next] [✅] Check abuse rate limits
4[dev:next] [⏳] Lookup user by email
5[dev:next] [✅] Lookup user by email
6[dev:next] [⏳] Save reset token
7[dev:next] [✅] Save reset token
8[dev:next] [⏳] Call Mail Service API
9[dev:next] [💥] Call Mail Service API Timed Out (7s)

Enter the Taskless Queue

Queues in Taskless are small, publicly accessible functions in your app. We recommend making them available inside of a queues directory; wherever your data endpoints go best. We've taken the slow parts (literally) of our API endpoint and put them inside of a Taskless runner. For Next.js, this would be /pages/api/queues/reset.ts:

1import { createQueue } from "@taskless/next";
2import { performSlowGlitchyPasswordReset } from "../auth/reset-slow";
3
4interface ResetData {
5 email: string;
6}
7
8export default createQueue<ResetData>(
9 "password-reset",
10 "/api/queues/reset",
11 async (job, api) => {
12 console.info("⤵️ Running Taskless Background Job");
13 await performSlowGlitchyPasswordReset();
14 return { ok: true };
15 }
16);

It's our same glitchy and unreliable password reset service, but this time it's bundled up in Taskless. Check the Use Taskless box on our password reset form, and the reset request will be sent to /api/auth/reset-fast.

1import type { NextApiRequest, NextApiResponse } from "next";
2import crypto from "crypto";
3import reset from "../queues/reset";
4
5type Data = {
6 ok: boolean;
7};
8
9const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {
10 console.info("⤵️ Making request via reset-fast");
11
12 if (!req.body.email) {
13 throw new Error("no email");
14 }
15 const email = (
16 Array.isArray(req.body.email) ? req.body.email : [req.body.email]
17 )[0];
18
19 const key = crypto
20 .createHash("sha256")
21 .update(email ?? "")
22 .digest("hex")
23 .toString();
24
25 await reset.enqueue(key, { email }, { retries: 3 });
26
27 console.info("[✅] Sent to Taskless");
28 res.status(200).json({ ok: true });
29};
30
31export default handler;

Now, when "Use Taskless" is selected, we'll push everything to the background. Even if our service is slow to respond, the user always gets an immediate response from our API. When our flaky Mail Service fails, Taskless backs off for a few seconds and tries again.

1[dev:next] ⤵️ Making request via reset-fast
2[dev:next] [✅] Sent to Taskless
3[dev:next] ⤵️ Running Taskless Background Job
4[dev:next] [⏳] Check abuse rate limits
5[dev:next] [✅] Check abuse rate limits
6[dev:next] [⏳] Lookup user by email
7[dev:next] [✅] Lookup user by email
8[dev:next] [⏳] Save reset token
9[dev:next] [✅] Save reset token
10[dev:next] [⏳] Call Mail Service API
11[dev:next] [💥] Call Mail Service API Timed Out (7s)
12[dev:next] Error: Service Timed Out
13[dev:next] at ...
14[dev:t ] info - 17:10:41.41 (root) FAIL of c496c6be-cc69-507f-bd14-4e4380be8945
15[dev:t ] error - 17:10:41.41 (root) Received non-2xx response
16[dev:next] ⤵️ Running Taskless Background Job
17[dev:next] [⏳] Check abuse rate limits
18[dev:next] [✅] Check abuse rate limits
19[dev:next] [⏳] Lookup user by email
20[dev:next] [✅] Lookup user by email
21[dev:next] [⏳] Save reset token
22[dev:next] [✅] Save reset token
23[dev:next] [⏳] Call Mail Service API
24[dev:next] [✅] Call Mail Service API
25[dev:t ] info - 17:11:15.745 (root) ACK of c496c6be-cc69-507f-bd14-4e4380be8945

fail-success

What's Next

Background jobs are just one of the many things you can do with Taskless. Check out the getting started doc for other guides and ideas.