DEV Community

Cover image for ๐Ÿงžโ€โ™‚๏ธ Generator unlocked: Create memes with ChatGPT and NextJS ๐Ÿš€ ๐Ÿ’ฅ
Eric Allam for Trigger.dev

Posted on • Updated on • Originally published at trigger.dev

๐Ÿงžโ€โ™‚๏ธ Generator unlocked: Create memes with ChatGPT and NextJS ๐Ÿš€ ๐Ÿ’ฅ

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.

Memes


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

GiveStar


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.

npx create-next-app memes-generator
Enter fullscreen mode Exit fullscreen mode

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.

Final

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.

Thank you

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.

npm install react-tag-input
Enter fullscreen mode Exit fullscreen mode

Copy the code snippet below into the index.js file.

"use client";
import { Space_Grotesk } from "next/font/google";
import { useState } from "react";
import { WithContext as ReactTags } from "react-tag-input";
import { useRouter } from "next/navigation";
import ViewMemes from "@/components/ViewMemes";

const KeyCodes = {
  comma: 188,
  enter: 13,
};
const inter = Space_Grotesk({ subsets: ["latin"] });
const delimiters = [KeyCodes.comma, KeyCodes.enter];

export default function Home() {
  const [audience, setAudience] = useState("");
  const [email, setEmail] = useState("");
  const [memes, setMemes] = useState([]);
  const router = useRouter();
  const [topics, setTopics] = useState([
    { id: "Developers", text: "Developers" },
  ]);

  const handleDelete = (i) =>
    setTopics(topics.filter((topic, index) => index !== i));

  const handleAddition = (topic) => setTopics([...topics, topic]);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      audience,
      topics,
      email,
    });
    router.push("/submit");
  };

  return (
    <main className={`w-full min-h-screen ${inter.className}`}>
      <header className="bg-[#F8F0E5] min-h-[95vh] flex items-center justify-center flex-col">
        <h2 className="text-4xl font-bold mb-2 text-[#0F2C59]">Meme Magic</h2>
        <h3 className="text-lg opacity-60 mb-8">
          Creating memes with a touch of magic
        </h3>
        <form
          className="flex flex-col md:w-[70%] w-[95%]"
          onSubmit={handleSubmit}
        >
          <label htmlFor="audience">Audience</label>
          <input
            type="text"
            name="audience"
            value={audience}
            required
            className="px-4 py-2 rounded mb-4"
            onChange={(e) => setAudience(e.target.value)}
          />
          <label htmlFor="email">Your email address</label>
          <input
            type="email"
            name="email"
            value={email}
            required
            className="px-4 py-2 rounded mb-4"
            onChange={(e) => setEmail(e.target.value)}
          />

          <ReactTags
            tags={topics}
            delimiters={delimiters}
            handleDelete={handleDelete}
            handleAddition={handleAddition}
            inputFieldPosition="top"
            autocomplete
            placeholder="Enter a topic for the meme and press enter"
          />
          <button
            type="submit"
            className="bg-[#0F2C59] text-[#fff] mt-[30px] py-4 rounded text-lg font-bold hover:bg-[#151d2b]"
          >
            GENERATE MEME
          </button>
        </form>
      </header>
      <ViewMemes memes={memes} />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

TagInput

Next, create a components folder containing the ViewMemes.jsx file, where all the recently generated memes are displayed.

import React from "react";
import Image from "next/image";

const ViewMemes = ({ memes }) => {
  return (
    <section className="h-[100vh] px-4 py-10 flex items-center flex-col">
      <h2 className="text-3xl font-bold mb-8">Recent Memes</h2>
      <div className="w-full flex flex-wrap md:space-x-4">
        {memes.map((meme) => (
          <div
            className="md:w-[30%] w-full m-2 flex flex-col items-center"
            key={meme.id}
          >
            <Image
              src={`${meme.meme_url}`}
              alt={meme.name}
              className="hover:scale-105 rounded"
              width={400}
              height={400}
            />
          </div>
        ))}
      </div>
    </section>
  );
};

export default ViewMemes;
Enter fullscreen mode Exit fullscreen mode

Meme submission ๐Ÿค

Create a submit.js file that displays a "Thank You" message to the user after submitting the form.

import Link from "next/link";
import React from "react";

export default function Submit() {
  return (
    <div className="w-full h-screen flex flex-col items-center justify-center">
      <h2 className="font-extrabold text-4xl mb-4 text-[#DAC0A3]">
        Thank You!
      </h2>
      <p className="mb-6">
        Your newly generated meme has been sent to your email.
      </p>
      <Link href="/">
        <p className="text-[#662549]">Go Home</p>
      </Link>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

Prod

Lastly, follow the steps shown to you.

Cli

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.

DEV Api

Copy your DEV server API key and run the code snippet below to install Trigger.dev. Follow the instructions carefully.

npx @trigger.dev/cli@latest init
Enter fullscreen mode Exit fullscreen mode

Start your Next.js project.

npm run dev
Enter fullscreen mode Exit fullscreen mode

Also, in another terminal, run the code snippet below to create a tunnel between the Trigger.dev and your Next.js project.

npx @trigger.dev/cli@latest dev
Enter fullscreen mode Exit fullscreen mode

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.

//๐Ÿ‘‡๐Ÿป runs when a user submits the form
const handleSubmit = (e) => {
  e.preventDefault();
  if (topics.length > 0) {
    let topic = "";
    topics.forEach((tag) => {
      topic += tag.text + ", ";
    });
    //๐Ÿ‘‡๐Ÿป sends the data to the backend
    postData(topic);
    router.push("/submit");
  }
};

//๐Ÿ‘‡๐Ÿป sends the data to the backend server
const postData = async (topic) => {
  try {
    const data = await fetch("/api/api", {
      method: "POST",
      body: JSON.stringify({
        audience,
        topic,
        email,
      }),
      headers: {
        "Content-Type": "application/json",
      },
    });
    const response = await data.json();
    console.log(response);
  } catch (err) {
    console.error(err);
  }
};
Enter fullscreen mode Exit fullscreen mode

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.

export default function handler(req, res) {
  const { topic, audience, email } = req.body;

  if (topic && audience && email) {
    console.log({ audience, topic, email });
  }
  res.status(200).json({ message: "Successful" });
}
Enter fullscreen mode Exit fullscreen mode

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.

client.defineJob({
  id: "generate-meme",
  name: "Generate Meme",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "generate.meme",
  }),
  run: async (payload, io, ctx) => {
    const { audience, topic, email } = payload;
    // This logs a message to the console and adds an entry to the run dashboard
    await io.logger.info("Meme request received!โœ…");
    await io.logger.info("Meme generation in progress ๐Ÿค");

    // Wrap your code in io.runTask to get automatic error handling and logging
    const selectedTemplate = await io.runTask("fetch-meme", async () => {
      const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");
      const memesData = await fetchAllMeme.json();
      const memes = memesData.data.memes;

      const randInt = Math.floor(Math.random() * 101);

      return memes[randInt];
    });

    const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;

    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.`;

    await io.logger.info("โœจ Yay! You've gotten a template for the meme โœจ", {
      userPrompt,
      sysPrompt,
      selectedTemplate,
      email,
    });
  },
});
Enter fullscreen mode Exit fullscreen mode

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.

import { client } from "@/trigger";

export default function handler(req, res) {
  const { topic, audience, email } = req.body;
  try {
    async function fetchMeme() {
      if (topic && audience && email) {
        await client.sendEvent({
          name: "generate.meme",
          payload: {
            audience,
            topic,
            email,
          },
        });
      }
    }
    fetchMeme();
  } catch (err) {
    return res.status(400).json({ message: err });
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Oncesuccessful

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.

client.defineJob({
  id: "generate-meme",
  name: "Generate Meme",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "generate.meme",
  }),
  run: async (payload, io, ctx) => {
    const { audience, topic, email } = payload;
    // This logs a message to the console and adds an entry to the run dashboard
    await io.logger.info("Meme request received!โœ…");
    await io.logger.info("Meme generation in progress ๐Ÿค");

    // Wrap your code in io.runTask to get automatic error handling and logging
    const selectedTemplate = await io.runTask("fetch-meme", async () => {
      const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");
      const memesData = await fetchAllMeme.json();
      const memes = memesData.data.memes;

      const randInt = Math.floor(Math.random() * 101);

      return memes[randInt];
    });

    const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;

    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.`;

    // Trigger any job listening for the gpt.text event
    await io.sendEvent("generate-gpt-text", {
      name: "gpt.text",
      payload: {
        userPrompt,
        sysPrompt,
        selectedTemplate,
        email,
      },
    });
  },
});
Enter fullscreen mode Exit fullscreen mode

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.

ChatGPT

Click View API key from the dropdown to create an API Key.

ApiKey

Next, install the OpenAI package by running the code snippet below.

npm install @trigger.dev/openai
Enter fullscreen mode Exit fullscreen mode

Add your OpenAI API key to the .env.local file.

OPENAI_API_KEY=<your_api_key>
Enter fullscreen mode Exit fullscreen mode

Import the OpenAI package into the jobs/functions.js file.

import { OpenAI } from "@trigger.dev/openai";

const openai = new OpenAI({ id: "openai", apiKey: process.env.OPENAI_API_KEY });
Enter fullscreen mode Exit fullscreen mode

Finally, create the job that generates the meme captions viaย OpenAI function call.

client.defineJob({
  id: "chatgpt-meme-text",
  name: "ChatGPT Meme Text",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "gpt.text",
  }),
  run: async (payload, io, ctx) => {
    const { userPrompt, sysPrompt, selectedTemplate, email } = payload;
    await io.logger.info("โœจ Talking to ChatGPT ๐Ÿซ‚");
    const messages = [
      { role: "system", content: sysPrompt },
      { role: "user", content: userPrompt },
    ];
    const functions = [
      {
        name: "generateMemeImage",
        description:
          "Generate meme via the imgflip API based on the given idea",
        parameters: {
          type: "object",
          properties: {
            text0: {
              type: "string",
              description: "The text for the top caption of the meme",
            },
            text1: {
              type: "string",
              description: "The text for the bottom caption of the meme",
            },
          },
          required: ["templateName", "text0", "text1"],
        },
      },
    ];
    const response = await openai.chat.completions.create("create-meme", {
      model: "gpt-3.5-turbo",
      messages,
      functions,
      function_call: "auto",
    });

    const responseMessage = response.choices[0];
    const texts = extractSentencesInQuotes(responseMessage.message.content);

    await io.logger.info("โœจ Yay! You've gotten a text for your meme โœจ", {
      texts,
    });

    await io.sendEvent("caption-save-meme", {
      name: "caption.save.meme",
      payload: {
        texts: ["Text0", "Text1"],
        selectedTemplate,
        email,
      },
    });
  },
});
Enter fullscreen mode Exit fullscreen mode

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.

const extractSentencesInQuotes = (inputString) => {
  const sentenceRegex = /"([^"]+)"/g;
  const sentences = [];
  let match;
  while ((match = sentenceRegex.exec(inputString))) {
    sentences.push(match[1]);
  }
  return sentences;
};
Enter fullscreen mode Exit fullscreen mode

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 - text0and text1. Text0 is the Top text for the meme, and Text1 is the Bottom text for the meme.

Text0

Create an account onย ImgFlip, and save your username and password into the .env.local file.

IMGFLIP_USERNAME=<your_username>
IMGFLIP_PW=<your_password>
Enter fullscreen mode Exit fullscreen mode

Add a new job to the jobs/functions.js file that adds a caption to the meme and saves it to Supabase.

client.defineJob({
  id: "caption-save-meme",
  name: "Caption and Save Meme",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "caption.save.meme",
  }),
  run: async (payload, io, ctx) => {
    const { texts, selectedTemplate, email } = payload;

    await io.logger.info("Received meme template and texts ๐ŸŽ‰");

    const formatData = new URLSearchParams({
      template_id: selectedTemplate.id,
      username: process.env.IMGFLIP_USERNAME,
      password: process.env.IMGFLIP_PW,
      text0: texts[0],
      text1: texts[1],
    });

    const captionedMeme = await io.runTask("caption-meme", async () => {
      const response = await fetch("https://api.imgflip.com/caption_image", {
        method: "POST",
        body: formatData.toString(),
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      });
      return await response.json();
    });

    await io.logger.info("โœจ Yay! Your meme has been captioned! โœจ", {
      captionedMeme,
    });
  },
});
Enter fullscreen mode Exit fullscreen mode

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.

CreatOrg

Next, create a new table containing the following columns, as shown below, and add some dummy data to the database table.

Org

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.

npm install @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode

Click API on the sidebar menu and copy the project's URL and API into the .env.local file.

NEXT_PUBLIC_SUPABASE_URL=<public_supabase_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<supabase_anon_key>
Enter fullscreen mode Exit fullscreen mode

Finally

Finally, create a src/supbaseClient.js file and create the Supabase client for the application.

import { createClient } from "@supabase/supabase-js";

const supabaseURL = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseURL, supabaseAnonKey, {
  auth: { persistSession: false },
});
Enter fullscreen mode Exit fullscreen mode

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.

import { supabase } from "@/supabaseClient";

export default function handler(req, res) {
  const fetchMemes = async () => {
    try {
      const { data, error } = await supabase
        .from("memes")
        .select("*")
        .order("created_at", { ascending: false });
      res.status(200).json({ data });
    } catch (err) {
      res.status(400).json({ error: err });
    }
  };
  fetchMemes();
}
Enter fullscreen mode Exit fullscreen mode

Then, send a request to the endpoint when a user visits the application's homepage.

useEffect(() => {
  fetchMemes();
}, []);

const fetchMemes = async () => {
  try {
    const request = await fetch("/api/all-memes");
    const response = await request.json();
    setMemes(response.data);
  } catch (err) {
    console.error(err);
  }
};
Enter fullscreen mode Exit fullscreen mode

Saving the memes to Supabase

Import Supabase from the @/supabaseClient file into the @/jobs/functions.js file.

import { supabase } from "@/supabaseClient";
Enter fullscreen mode Exit fullscreen mode

Update the recently added job to save the meme to Supabase.

client.defineJob({
  id: "caption-save-meme",
  name: "Caption and Save Meme",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "caption.save.meme",
  }),
  run: async (payload, io, ctx) => {
    const { texts, selectedTemplate, email } = payload;

    await io.logger.info("Received meme template and texts ๐ŸŽ‰");

    const formatData = new URLSearchParams({
      template_id: selectedTemplate.id,
      username: process.env.IMGFLIP_USERNAME,
      password: process.env.IMGFLIP_PW,
      text0: texts[0],
      text1: texts[1],
    });

    const captionedMeme = await io.runTask("caption-meme", async () => {
      const response = await fetch("https://api.imgflip.com/caption_image", {
        method: "POST",
        body: formatData.toString(),
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      });
      return await response.json();
    });

    await io.logger.info("โœจ Yay! Your meme has been captioned! โœจ", {
      captionedMeme,
    });

    await supabase.from("memes").insert({
      id: selectedTemplate.id,
      name: selectedTemplate.name,
      meme_url: captionedMeme.data.url,
    });

    await io.sendEvent("email-meme", {
      name: "send.meme",
      payload: {
        email,
        meme_url: `http://localhost:3000/memes/${selectedTemplate.id}`,
      },
    });

    await io.logger.info(
      "โœจ Yay! Your meme has been saved to the database! โœจ"
    );
  },
});
Enter fullscreen mode Exit fullscreen mode

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.

RESEND_API_KEY=<place_your_API_key>
Enter fullscreen mode Exit fullscreen mode

Resize

Install the Trigger.dev Resend integration:

npm i @trigger.dev/resend
Enter fullscreen mode Exit fullscreen mode

Import Resend into the @/jobs/functions.js file as shown below.

import { Resend } from "@trigger.dev/resend";
const resend = new Resend({ id: "resend", apiKey: process.env.RESEND_API_KEY });
Enter fullscreen mode Exit fullscreen mode

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.

client.defineJob({
  id: "send-meme",
  name: "Send Meme",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "send.meme",
  }),
  run: async (payload, io, ctx) => {
    const { meme_url, email } = payload;

    await io.logger.info("Sending meme to the user ๐ŸŽ‰");

    await resend.sendEmail("๐Ÿ“ง", {
      from: "onboarding@resend.dev",
      to: [email],
      subject: "Your meme is ready!",
      text: `Hey there, Your meme is ready.\n Access it here: ${meme_url}`,
    });

    await io.logger.info("โœจ Yay! Your meme has been emailed to the user! โœจ");
  },
});
Enter fullscreen mode Exit fullscreen mode

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.

ThisIsFine

Copy the code snippet below into the memes/[id].js file.

"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import { usePathname } from "next/navigation";
import Link from "next/link";

export default function Meme() {
  const params = usePathname();
  const [meme, setMeme] = useState({});

  useEffect(() => {
    const fetchMeme = async () => {
      if (params) {
        const id = params.split("/")[2];
        const request = await fetch("/api/meme", {
          method: "POST",
          body: JSON.stringify({ id }),
          headers: {
            "Content-Type": "application/json",
          },
        });
        const response = await request.json();
        setMeme(response.data[0]);
      }
    };

    fetchMeme();
  }, [params]);

  return (
    <div className="w-full min-h-screen flex flex-col items-center justify-center">
      {meme?.meme_url && (
        <Image
          src={meme.meme_url}
          alt={meme.name}
          className="hover:scale-105 rounded"
          width={500}
          height={500}
        />
      )}

      <Link href="/" className="mt-6 text-blue-500">
        Go back home
      </Link>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

import { supabase } from "@/supabaseClient";

export default function handler(req, res) {
  const getMeme = async () => {
    const { id } = req.body;
    try {
      const { data, error } = await supabase
        .from("memes")
        .select("*")
        .eq("id", parseInt(id));
      res.status(200).json({ data });
    } catch (err) {
      res.status(400).json({ error: err });
    }
  };
  getMeme();
}
Enter fullscreen mode Exit fullscreen mode

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

GiveStar

Top comments (9)

Collapse
 
nevodavid profile image
Nevo David

Great article!
I love how Trigger.dev can take all the background jobs without using an backend.

Collapse
 
pizofreude profile image
Pizofreude

Great tutorial with step-by-step instructions. This is bananas.

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Nice article!
Super informative :)

Collapse
 
mattaitken profile image
Matt Aitken

Brilliant! I'm already churning out memes ๐Ÿคฃ

Collapse
 
talboren profile image
Tal Borenstein

Love that ๐Ÿ˜‚ Thanks!

Collapse
 
vincanger profile image
vincanger

Please do not copy my tutorials

Collapse
 
neilsentence profile image
Nils Sens
Collapse
 
srbhr profile image
Saurabh Rai

Cool article. I've saved it and will go through the whole tutorial this weekend.
My Monday Memes are coming ๐Ÿ˜‚โœ…

Collapse
 
allen121 profile image
allen