Generator unlocked: Create memes with ChatGPT and NextJS
CTO, Trigger.dev
TL;DR
In this tutorial, you'll learn how to build a meme generator in Next.js. We will be using:
- Trigger.dev for handling the meme's creation in the background
- Supabase for storing and saving the memes
- Resend for sending the generated meme to the user
Your background job management for NextJS
Trigger.dev is an open-source library that enables you to create and monitor long-running jobs for your app with NextJS, Remix, Astro, and so many more!
If you can spend 10 seconds giving us a star, I would be super grateful π https://github.com/triggerdotdev/trigger.dev
Let's set it up π₯
Here, I'll walk you through creating the user interface for the meme generator application.
Create a new Next.js project by running the code snippet below.
_10npx create-next-app memes-generator
First, you need to create a homepage displaying a form that enables users to enter the meme audience and topic. You can also add another field for the user's email address to receive the meme once it is ready. Below the form, users should be able to view the recently generated memes, as shown below.
When the user submits the form, they will be redirected to a submission page while the application generates the meme in the background and sends it to the user's email.
Home page π
Before we proceed, run the code snippet below to install the React Tag Input package. It enables us to provide multiple inputs for a value.
_10npm install react-tag-input
Copy the code snippet below into the index.js file.
_89"use client";_89import { Space_Grotesk } from "next/font/google";_89import { useState } from "react";_89import { WithContext as ReactTags } from "react-tag-input";_89import { useRouter } from "next/navigation";_89import ViewMemes from "@/components/ViewMemes";_89_89const KeyCodes = {_89 comma: 188,_89 enter: 13,_89};_89const inter = Space_Grotesk({ subsets: ["latin"] });_89const delimiters = [KeyCodes.comma, KeyCodes.enter];_89_89export default function Home() {_89 const [audience, setAudience] = useState("");_89 const [email, setEmail] = useState("");_89 const [memes, setMemes] = useState([]);_89 const router = useRouter();_89 const [topics, setTopics] = useState([_89 { id: "Developers", text: "Developers" },_89 ]);_89_89 const handleDelete = (i) =>_89 setTopics(topics.filter((topic, index) => index !== i));_89_89 const handleAddition = (topic) => setTopics([...topics, topic]);_89_89 const handleSubmit = (e) => {_89 e.preventDefault();_89 console.log({_89 audience,_89 topics,_89 email,_89 });_89 router.push("/submit");_89 };_89_89 return (_89 <main className={`min-h-screen w-full ${inter.className}`}>_89 <header className="flex min-h-[95vh] flex-col items-center justify-center bg-[#F8F0E5]">_89 <h2 className="mb-2 text-4xl font-bold text-[#0F2C59]">Meme Magic</h2>_89 <h3 className="mb-8 text-lg opacity-60">_89 Creating memes with a touch of magic_89 </h3>_89 <form_89 className="flex w-[95%] flex-col md:w-[70%]"_89 onSubmit={handleSubmit}_89 >_89 <label htmlFor="audience">Audience</label>_89 <input_89 type="text"_89 name="audience"_89 value={audience}_89 required_89 className="mb-4 rounded px-4 py-2"_89 onChange={(e) => setAudience(e.target.value)}_89 />_89 <label htmlFor="email">Your email address</label>_89 <input_89 type="email"_89 name="email"_89 value={email}_89 required_89 className="mb-4 rounded px-4 py-2"_89 onChange={(e) => setEmail(e.target.value)}_89 />_89_89 <ReactTags_89 tags={topics}_89 delimiters={delimiters}_89 handleDelete={handleDelete}_89 handleAddition={handleAddition}_89 inputFieldPosition="top"_89 autocomplete_89 placeholder="Enter a topic for the meme and press enter"_89 />_89 <button_89 type="submit"_89 className="mt-[30px] rounded bg-[#0F2C59] py-4 text-lg font-bold text-[#fff] hover:bg-[#151d2b]"_89 >_89 GENERATE MEME_89 </button>_89 </form>_89 </header>_89 <ViewMemes memes={memes} />_89 </main>_89 );_89}
The code snippet creates input fields that enable users to enter the email, audience, and topic of the meme to be generated. The <ReactTags>
component allows us to enter multiple values as the meme topic.
Next, create a components folder containing the ViewMemes.jsx file, where all the recently generated memes are displayed.
_28import React from "react";_28import Image from "next/image";_28_28const ViewMemes = ({ memes }) => {_28 return (_28 <section className="flex h-[100vh] flex-col items-center px-4 py-10">_28 <h2 className="mb-8 text-3xl font-bold">Recent Memes</h2>_28 <div className="flex w-full flex-wrap md:space-x-4">_28 {memes.map((meme) => (_28 <div_28 className="m-2 flex w-full flex-col items-center md:w-[30%]"_28 key={meme.id}_28 >_28 <Image_28 src={`${meme.meme_url}`}_28 alt={meme.name}_28 className="rounded hover:scale-105"_28 width={400}_28 height={400}_28 />_28 </div>_28 ))}_28 </div>_28 </section>_28 );_28};_28_28export default ViewMemes;
Meme submission π€
Create a submit.js
file that displays a "Thank You" message to the user after submitting the form.
_18import Link from "next/link";_18import React from "react";_18_18export default function Submit() {_18 return (_18 <div className="flex h-screen w-full flex-col items-center justify-center">_18 <h2 className="mb-4 text-4xl font-extrabold text-[#DAC0A3]">_18 Thank You!_18 </h2>_18 <p className="mb-6">_18 Your newly generated meme has been sent to your email._18 </p>_18 <Link href="/">_18 <p className="text-[#662549]">Go Home</p>_18 </Link>_18 </div>_18 );_18}
Generate memes in the background π₯
In this section, I will walk you through adding Trigger.dev to a Next.js project. First, you need to visit the Trigger.dev homepage and create a new account.
Next, create an organization and project name for your jobs.
Lastly, follow the steps shown to you.
If this is not your initial Trigger.dev project, follow the steps below.
Click Environments & API Keys
on the sidebar menu of your project dashboard.
Copy your DEV server API key and run the code snippet below to install Trigger.dev. Follow the instructions carefully.
_10npx @trigger.dev/cli@latest init
Start your Next.js project.
_10npm run dev
Also, in another terminal, run the code snippet below to create a tunnel between the Trigger.dev and your Next.js project.
_10npx @trigger.dev/cli@latest dev
Finally, rename the jobs/examples.js
file to jobs/functions.js
. This is where all the jobs are processed.
Congratulations!π You've successfully added Trigger.dev to your Next.js app.
How to generate meme templates and captions with OpenAI and ImgFlip
In this section, you'll learn how to fetch meme templates from ImgFlip and generate meme captions from OpenAI via Trigger.dev.
Before we proceed, update the index.js
file to send the user's input to the server.
_34//ππ» runs when a user submits the form_34const handleSubmit = (e) => {_34 e.preventDefault();_34 if (topics.length > 0) {_34 let topic = "";_34 topics.forEach((tag) => {_34 topic += tag.text + ", ";_34 });_34 //ππ» sends the data to the backend_34 postData(topic);_34 router.push("/submit");_34 }_34};_34_34//ππ» sends the data to the backend server_34const postData = async (topic) => {_34 try {_34 const data = await fetch("/api/api", {_34 method: "POST",_34 body: JSON.stringify({_34 audience,_34 topic,_34 email,_34 }),_34 headers: {_34 "Content-Type": "application/json",_34 },_34 });_34 const response = await data.json();_34 console.log(response);_34 } catch (err) {_34 console.error(err);_34 }_34};
The postData
function sends the user's input to the /api/api
endpoint on the server when the user submits the form.
Next, create an api.js
file within the api
folder that accepts the user's data.
_10export default function handler(req, res) {_10 const { topic, audience, email } = req.body;_10_10 if (topic && audience && email) {_10 console.log({ audience, topic, email });_10 }_10 res.status(200).json({ message: "Successful" });_10}
Meme background job π¨π»βπ§
There are three ways of communicating via Trigger which are webhook, schedule, and event. Webhook triggers a job in real-time when specific events occur, Schedule works for repeating tasks, and Events trigger a job when you send a payload.
Here, I'll guide you through using Event triggers to execute jobs in the application.
Within the jobs/functions.js
file, update the client.defineJob
function as done below.
_36client.defineJob({_36 id: "generate-meme",_36 name: "Generate Meme",_36 version: "0.0.1",_36 trigger: eventTrigger({_36 name: "generate.meme",_36 }),_36 run: async (payload, io, ctx) => {_36 const { audience, topic, email } = payload;_36 // This logs a message to the console and adds an entry to the run dashboard_36 await io.logger.info("Meme request received!β
");_36 await io.logger.info("Meme generation in progress π€");_36_36 // Wrap your code in io.runTask to get automatic error handling and logging_36 const selectedTemplate = await io.runTask("fetch-meme", async () => {_36 const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");_36 const memesData = await fetchAllMeme.json();_36 const memes = memesData.data.memes;_36_36 const randInt = Math.floor(Math.random() * 101);_36_36 return memes[randInt];_36 });_36_36 const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;_36_36 const sysPrompt = `You are a meme idea generator. You will use the imgflip api to generate a meme based on an idea you suggest. Given a random template name and topics, generate a meme idea for the intended audience. Only use the template provided.`;_36_36 await io.logger.info("β¨ Yay! You've gotten a template for the meme β¨", {_36 userPrompt,_36 sysPrompt,_36 selectedTemplate,_36 email,_36 });_36 },_36});
The code snippet above creates a new job that fetches a random meme template from ImgFlip, creates a user, and a system prompt for OpenAI to generate a meme caption based on the data provided by the user.
To trigger this job, update the /api/api.js
file to send the user's data as a payload to the job.
_22import { client } from "@/trigger";_22_22export default function handler(req, res) {_22 const { topic, audience, email } = req.body;_22 try {_22 async function fetchMeme() {_22 if (topic && audience && email) {_22 await client.sendEvent({_22 name: "generate.meme",_22 payload: {_22 audience,_22 topic,_22 email,_22 },_22 });_22 }_22 }_22 fetchMeme();_22 } catch (err) {_22 return res.status(400).json({ message: err });_22 }_22}
The code snippet above triggers the job via its name - generate.meme and sends the audience, topic, and the user's email as a payload to the job.
Once this is successful, you should see the status of the job on your Trigger.dev dashboard and monitor its activity.
Trigger.dev also allows us to nest jobs within jobs; that is, you can trigger another job within a job. This makes it possible for you to trigger a job that will execute all the nested jobs within itself.
In the previous job, we logged the data retrieved to the console; Next, let's trigger another job within the job.
_40client.defineJob({_40 id: "generate-meme",_40 name: "Generate Meme",_40 version: "0.0.1",_40 trigger: eventTrigger({_40 name: "generate.meme",_40 }),_40 run: async (payload, io, ctx) => {_40 const { audience, topic, email } = payload;_40 // This logs a message to the console and adds an entry to the run dashboard_40 await io.logger.info("Meme request received!β
");_40 await io.logger.info("Meme generation in progress π€");_40_40 // Wrap your code in io.runTask to get automatic error handling and logging_40 const selectedTemplate = await io.runTask("fetch-meme", async () => {_40 const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");_40 const memesData = await fetchAllMeme.json();_40 const memes = memesData.data.memes;_40_40 const randInt = Math.floor(Math.random() * 101);_40_40 return memes[randInt];_40 });_40_40 const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;_40_40 const sysPrompt = `You are a meme idea generator. You will use the imgflip api to generate a meme based on an idea you suggest. Given a random template name and topics, generate a meme idea for the intended audience. Only use the template provided.`;_40_40 // Trigger any job listening for the gpt.text event_40 await io.sendEvent("generate-gpt-text", {_40 name: "gpt.text",_40 payload: {_40 userPrompt,_40 sysPrompt,_40 selectedTemplate,_40 email,_40 },_40 });_40 },_40});
The code snippet above sends the data retrieved from the job as a payload to an event - where you can generate the meme caption. Each event has a unique name, and any job that is triggered by an event with a matching name will be run. You can send events using the io.sendEvent method.
Now that we are selecting a template and generate prompts, we can implement the job for generating meme text by listening for the gpt.text event.
Generate the meme text with ChatGPT π€
Here, you'll learn how to communicate with OpenAI using the Trigger.dev OpenAI integration. We'll use function calls for generating captions for the meme templates. Before we proceed, create an OpenAI account and generate an API Key.
Click View API key
from the dropdown to create an API Key.
Next, install the OpenAI package by running the code snippet below.
_10npm install @trigger.dev/openai
Add your OpenAI API key to the .env.local
file.
_10OPENAI_API_KEY=<your_api_key>
Import the OpenAI package into the jobs/functions.js
file.
_10import { OpenAI } from "@trigger.dev/openai";_10_10const openai = new OpenAI({ id: "openai", apiKey: process.env.OPENAI_API_KEY });
Finally, create the job that generates the meme captions via OpenAI function call.
_59client.defineJob({_59 id: "chatgpt-meme-text",_59 name: "ChatGPT Meme Text",_59 version: "0.0.1",_59 trigger: eventTrigger({_59 name: "gpt.text",_59 }),_59 run: async (payload, io, ctx) => {_59 const { userPrompt, sysPrompt, selectedTemplate, email } = payload;_59 await io.logger.info("β¨ Talking to ChatGPT π«");_59 const messages = [_59 { role: "system", content: sysPrompt },_59 { role: "user", content: userPrompt },_59 ];_59 const functions = [_59 {_59 name: "generateMemeImage",_59 description:_59 "Generate meme via the imgflip API based on the given idea",_59 parameters: {_59 type: "object",_59 properties: {_59 text0: {_59 type: "string",_59 description: "The text for the top caption of the meme",_59 },_59 text1: {_59 type: "string",_59 description: "The text for the bottom caption of the meme",_59 },_59 },_59 required: ["templateName", "text0", "text1"],_59 },_59 },_59 ];_59 const response = await openai.chat.completions.create("create-meme", {_59 model: "gpt-3.5-turbo",_59 messages,_59 functions,_59 function_call: "auto",_59 });_59_59 const responseMessage = response.choices[0];_59 const texts = extractSentencesInQuotes(responseMessage.message.content);_59_59 await io.logger.info("β¨ Yay! You've gotten a text for your meme β¨", {_59 texts,_59 });_59_59 await io.sendEvent("caption-save-meme", {_59 name: "caption.save.meme",_59 payload: {_59 texts: ["Text0", "Text1"],_59 selectedTemplate,_59 email,_59 },_59 });_59 },_59});
The code snippet above generates a caption for the meme via the OpenAI function calling feature, extracts the caption, and passes it as a payload to another job. The payload contains the caption, selected meme template, and the user's email.
The extractSentencesInQuotes
function retrieves the caption from the data returned by OpenAI.
_10const extractSentencesInQuotes = (inputString) => {_10 const sentenceRegex = /"([^"]+)"/g;_10 const sentences = [];_10 let match;_10 while ((match = sentenceRegex.exec(inputString))) {_10 sentences.push(match[1]);_10 }_10 return sentences;_10};
So far, we've been able to select a meme template from ImgFlip and generate the meme caption via OpenAI. In the upcoming sections, you'll learn how to add the caption to the meme templates.
Set the memes title π
ImgFlip API provides a /caption_image
endpoint that accepts required parameters, such as template ID, username, password, and the captions - text0
and text1
. Text0
is the Top text for the meme, and Text1
is the Bottom text for the meme.
Create an account on ImgFlip, and save your username and password into the .env.local
file.
_10IMGFLIP_USERNAME=<your_username>_10IMGFLIP_PW=<your_password>
Add a new job to the jobs/functions.js
file that adds a caption to the meme and saves it to Supabase.
_36client.defineJob({_36 id: "caption-save-meme",_36 name: "Caption and Save Meme",_36 version: "0.0.1",_36 trigger: eventTrigger({_36 name: "caption.save.meme",_36 }),_36 run: async (payload, io, ctx) => {_36 const { texts, selectedTemplate, email } = payload;_36_36 await io.logger.info("Received meme template and texts π");_36_36 const formatData = new URLSearchParams({_36 template_id: selectedTemplate.id,_36 username: process.env.IMGFLIP_USERNAME,_36 password: process.env.IMGFLIP_PW,_36 text0: texts[0],_36 text1: texts[1],_36 });_36_36 const captionedMeme = await io.runTask("caption-meme", async () => {_36 const response = await fetch("https://api.imgflip.com/caption_image", {_36 method: "POST",_36 body: formatData.toString(),_36 headers: {_36 "Content-Type": "application/x-www-form-urlencoded",_36 },_36 });_36 return await response.json();_36 });_36_36 await io.logger.info("β¨ Yay! Your meme has been captioned! β¨", {_36 captionedMeme,_36 });_36 },_36});
The code snippet above accepts the required parameters and sends them to the endpoint provided by ImgFlip. The endpoint receives the meme ID, caption text, and user's credentials in a URL-encoded format.
In the upcoming section, you'll learn how to save the captioned meme to Supabase.
Save the memes and display them πΎ
Supabase is an open-source Firebase alternative that enables you to add authentication, file storage, Postgres, and real-time database to your software applications. With Supabase, you can build secured and scalable applications in a few minutes.
In this section, I'll guide you through setting up and interacting with a Supabase backend from a Next.js app.
Visit the Supabase homepage and create a new organization and project.
Next, create a new table containing the following columns, as shown below, and add some dummy data to the database table.
The table stores the id
, name
, and meme_url
properties received from the application. The created_at
is auto-generated and saves the data timestamp.
Run the code snippet below to install the Supabase package into Next.js.
_10npm install @supabase/supabase-js
Click API
on the sidebar menu and copy the project's URL and API into the .env.local
file.
_10NEXT_PUBLIC_SUPABASE_URL=<public_supabase_URL>_10NEXT_PUBLIC_SUPABASE_ANON_KEY=<supabase_anon_key>
Finally, create a src/supbaseClient.js
file and create the Supabase client for the application.
_10import { createClient } from "@supabase/supabase-js";_10_10const supabaseURL = process.env.NEXT_PUBLIC_SUPABASE_URL;_10const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;_10_10export const supabase = createClient(supabaseURL, supabaseAnonKey, {_10 auth: { persistSession: false },_10});
Congratulations!π You've successfully added Supabase to the Next.js application.
Fetching all the available memes
Create an api/all-memes
endpoint on the Next.js server that retrieves all the memes from Supabase.
_16import { supabase } from "@/supabaseClient";_16_16export default function handler(req, res) {_16 const fetchMemes = async () => {_16 try {_16 const { data, error } = await supabase_16 .from("memes")_16 .select("*")_16 .order("created_at", { ascending: false });_16 res.status(200).json({ data });_16 } catch (err) {_16 res.status(400).json({ error: err });_16 }_16 };_16 fetchMemes();_16}
Then, send a request to the endpoint when a user visits the application's homepage.
_13useEffect(() => {_13 fetchMemes();_13}, []);_13_13const fetchMemes = async () => {_13 try {_13 const request = await fetch("/api/all-memes");_13 const response = await request.json();_13 setMemes(response.data);_13 } catch (err) {_13 console.error(err);_13 }_13};
Saving the memes to Supabase
Import Supabase from the @/supabaseClient
file into the @/jobs/functions.js
file.
_10import { supabase } from "@/supabaseClient";
Update the recently added job to save the meme to Supabase.
_54client.defineJob({_54 id: "caption-save-meme",_54 name: "Caption and Save Meme",_54 version: "0.0.1",_54 trigger: eventTrigger({_54 name: "caption.save.meme",_54 }),_54 run: async (payload, io, ctx) => {_54 const { texts, selectedTemplate, email } = payload;_54_54 await io.logger.info("Received meme template and texts π");_54_54 const formatData = new URLSearchParams({_54 template_id: selectedTemplate.id,_54 username: process.env.IMGFLIP_USERNAME,_54 password: process.env.IMGFLIP_PW,_54 text0: texts[0],_54 text1: texts[1],_54 });_54_54 const captionedMeme = await io.runTask("caption-meme", async () => {_54 const response = await fetch("https://api.imgflip.com/caption_image", {_54 method: "POST",_54 body: formatData.toString(),_54 headers: {_54 "Content-Type": "application/x-www-form-urlencoded",_54 },_54 });_54 return await response.json();_54 });_54_54 await io.logger.info("β¨ Yay! Your meme has been captioned! β¨", {_54 captionedMeme,_54 });_54_54 await supabase.from("memes").insert({_54 id: selectedTemplate.id,_54 name: selectedTemplate.name,_54 meme_url: captionedMeme.data.url,_54 });_54_54 await io.sendEvent("email-meme", {_54 name: "send.meme",_54 payload: {_54 email,_54 meme_url: `http://localhost:3000/memes/${selectedTemplate.id}`,_54 },_54 });_54_54 await io.logger.info(_54 "β¨ Yay! Your meme has been saved to the database! β¨"_54 );_54 },_54});
The code snippet above adds a caption to the meme template and saves the newly created meme to Supabase. It also triggers another job, which sends the meme page URL to the user's email. You'll learn how to do this in the upcoming section.
Send email on completion π¬
Resend is an email API that enables you to send texts, attachments, and email templates easily. With Resend, you can build, test, and deliver transactional emails at scale.
Here, you'll learn how to send emails via Resend. Go to the Signup page and create an account.
Create an API Key
and save it into a .env.local
file within your Next.js project.
_10RESEND_API_KEY=<place_your_API_key>
Install the Trigger.dev Resend integration:
_10npm i @trigger.dev/resend
Import Resend into the @/jobs/functions.js
file as shown below.
_10import { Resend } from "@trigger.dev/resend";_10const resend = new Resend({ id: "resend", apiKey: process.env.RESEND_API_KEY });
Add a final job that receives the meme URL and user's email from the previous job and sends the newly generated meme to the user upon completion.
_22client.defineJob({_22 id: "send-meme",_22 name: "Send Meme",_22 version: "0.0.1",_22 trigger: eventTrigger({_22 name: "send.meme",_22 }),_22 run: async (payload, io, ctx) => {_22 const { meme_url, email } = payload;_22_22 await io.logger.info("Sending meme to the user π");_22_22 await resend.sendEmail("π§", {_22 from: "[email protected]",_22 to: [email],_22 subject: "Your meme is ready!",_22 text: `Hey there, Your meme is ready.\n Access it here: ${meme_url}`,_22 });_22_22 await io.logger.info("β¨ Yay! Your meme has been emailed to the user! β¨");_22 },_22});
The user receives an email containing a link in this format: http://localhost:3000/memes/${meme.id}
. This link redirects the user to a specific page in the application that displays the meme. We will create this page in the upcoming section.
Displaying the newly generated meme
Create a memes/[id].js
file that accepts the meme ID from the URL, get the meme from Supabase via its ID, and displays the meme on the page.
Copy the code snippet below into the memes/[id].js
file.
_47"use client";_47import React, { useEffect, useState } from "react";_47import Image from "next/image";_47import { usePathname } from "next/navigation";_47import Link from "next/link";_47_47export default function Meme() {_47 const params = usePathname();_47 const [meme, setMeme] = useState({});_47_47 useEffect(() => {_47 const fetchMeme = async () => {_47 if (params) {_47 const id = params.split("/")[2];_47 const request = await fetch("/api/meme", {_47 method: "POST",_47 body: JSON.stringify({ id }),_47 headers: {_47 "Content-Type": "application/json",_47 },_47 });_47 const response = await request.json();_47 setMeme(response.data[0]);_47 }_47 };_47_47 fetchMeme();_47 }, [params]);_47_47 return (_47 <div className="flex min-h-screen w-full flex-col items-center justify-center">_47 {meme?.meme_url && (_47 <Image_47 src={meme.meme_url}_47 alt={meme.name}_47 className="rounded hover:scale-105"_47 width={500}_47 height={500}_47 />_47 )}_47_47 <Link href="/" className="mt-6 text-blue-500">_47 Go back home_47 </Link>_47 </div>_47 );_47}
The code snippet above retrieves the meme ID from the URL, sends it to the server via the /api/meme
endpoint, and receives the meme details as a response.
Next, create the /api/meme
endpoint.
_17import { supabase } from "@/supabaseClient";_17_17export default function handler(req, res) {_17 const getMeme = async () => {_17 const { id } = req.body;_17 try {_17 const { data, error } = await supabase_17 .from("memes")_17 .select("*")_17 .eq("id", parseInt(id));_17 res.status(200).json({ data });_17 } catch (err) {_17 res.status(400).json({ error: err });_17 }_17 };_17 getMeme();_17}
The code snippet above fetches the data whose property matches the ID from Supabase.
Congratulations! You've completed the project.
Conclusion
So far, you've learnt how to create jobs within your codebase with Trigger, generate texts from OpenAI, save and retrieve data from Supabase, and send emails via Resend.
The source code for this tutorial is available here: https://github.com/triggerdotdev/blog/tree/main/memes-generator
Thank you for reading!
If you can spend 10 seconds giving us a star, I would be super grateful. π https://github.com/triggerdotdev/trigger.dev