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-guide3touch .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
,
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";67const inter = Inter({ subsets: ["latin"] });89export 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);1718 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 }3334 setProcessing(false);35 }, [processing]);3637 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 <div50 className={styles.grid}51 style={{ opacity: processing ? 0.5 : 1 }}52 >53 <input54 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 Password62 </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-slow2[dev:next] [⏳] Check abuse rate limits3[dev:next] [✅] Check abuse rate limits4[dev:next] [⏳] Lookup user by email5[dev:next] [✅] Lookup user by email6[dev:next] [⏳] Save reset token7[dev:next] [✅] Save reset token8[dev:next] [⏳] Call Mail Service API9[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";34interface ResetData {5 email: string;6}78export 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";45type Data = {6 ok: boolean;7};89const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {10 console.info("⤵️ Making request via reset-fast");1112 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];1819 const key = crypto20 .createHash("sha256")21 .update(email ?? "")22 .digest("hex")23 .toString();2425 await reset.enqueue(key, { email }, { retries: 3 });2627 console.info("[✅] Sent to Taskless");28 res.status(200).json({ ok: true });29};3031export 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-fast2[dev:next] [✅] Sent to Taskless3[dev:next] ⤵️ Running Taskless Background Job4[dev:next] [⏳] Check abuse rate limits5[dev:next] [✅] Check abuse rate limits6[dev:next] [⏳] Lookup user by email7[dev:next] [✅] Lookup user by email8[dev:next] [⏳] Save reset token9[dev:next] [✅] Save reset token10[dev:next] [⏳] Call Mail Service API11[dev:next] [💥] Call Mail Service API Timed Out (7s)12[dev:next] Error: Service Timed Out13[dev:next] at ...14[dev:t ] info - 17:10:41.41 (root) FAIL of c496c6be-cc69-507f-bd14-4e4380be894515[dev:t ] error - 17:10:41.41 (root) Received non-2xx response16[dev:next] ⤵️ Running Taskless Background Job17[dev:next] [⏳] Check abuse rate limits18[dev:next] [✅] Check abuse rate limits19[dev:next] [⏳] Lookup user by email20[dev:next] [✅] Lookup user by email21[dev:next] [⏳] Save reset token22[dev:next] [✅] Save reset token23[dev:next] [⏳] Call Mail Service API24[dev:next] [✅] Call Mail Service API25[dev:t ] info - 17:11:15.745 (root) ACK of c496c6be-cc69-507f-bd14-4e4380be8945
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.