How to Integrate Stability AI API in Next.js: Step-by-Step Guide

In this article, we will walk through integrating the Stability AI API in a Next.js application. Stability API is used for text-to-image generation and can generate images based on given text prompts. We’ll create a simple Next.js component that takes user input, makes an API call to the Stability API, and displays the generated image.

Prerequisites

Before we start, make sure you have the following:

  1. Node.js and npm are installed on your machine.
  2. A Next.js project is set up and ready to use.

If you haven’t created a Next.js project yet, you can do so by running the following commands:

npx create-next-app my-image-generator
cd my-image-generator

Now, let’s move on to integrating the Stability API.

Step 1: Set Up Stability API Client

Create a new file called client.js in the utils folder (if it doesn’t exist) to set up the Stability API client.

// utils/client.js
import axios from "axios";

const stabilityApiClient = axios.create({
  baseURL: "https://api.stability.ai/v1/generation/stable-diffusion-xl-beta-v2-2-2",
  headers: {
    Accept: "application/json",
    Authorization: "Bearer YOUR_API_KEY",
  },
});

export default stabilityApiClient;

Replace "YOUR_API_KEY" with your actual Stability API key. You can obtain the API key by signing up on the Stability AI website and accessing your API credentials.

Step 2: Create the ImageForm Component

Now, let’s create a new component called ImageForm that will handle user input and image generation.

// components/ImageForm.js
import { useState } from "react";
import stabilityApiClient from "../utils/client";

const ImageForm = () => {
  // State for form data and generated image
  const [formData, setFormData] = useState({
    positivePrompt: "",
    negativePrompt: "",
    modalStyle: "anime",
    cfgScale: 7,
    steps: 50,
  });
  const [imageURL, setImageURL] = useState(null);
  const [loading, setLoading] = useState(false);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      setLoading(true);
      const response = await stabilityApiClient.post("/text-to-image", {
        width: 512,
        height: 512,
        steps: parseInt(formData.steps),
        seed: 0,
        cfg_scale: parseInt(formData.cfgScale),
        samples: 1,
        style_preset: formData.modalStyle,
        text_prompts: [{ text: formData.positivePrompt, weight: 1 }],
      });

      const image = response.data.artifacts[0];
      const imageBuffer = Buffer.from(image.base64, "base64");
      const generatedImageURL = URL.createObjectURL(new Blob([imageBuffer]));

      setImageURL(generatedImageURL);
    } catch (error) {
      console.error("Error:", error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="container mx-auto py-9">
      <div className="grid gap-20 py-14 items-center grid-col-2 bg-orange-200 rounded-xl p-8 shadow-lg  sm:grid-cols-1 lg:grid-cols-2 border-top">
        <div>
          <form onSubmit={handleSubmit} className="space-y-4">
            <div>
              <label htmlFor="positivePrompt">Positive Prompt Text:</label>
              <textarea
                type="textarea"
                id="positivePrompt"
                minLength={4}
                rows={4}
                name="positivePrompt"
                value={formData.positivePrompt}
                onChange={handleChange}
                className="border border-gray-300 p-2 w-full"
              />
            </div>
            <div>
              <label htmlFor="negativePrompt">Negative Prompt Text:</label>
              <textarea
                type="textarea"
                id="negativePrompt"
                name="negativePrompt"
                minLength={4}
                rows={4}
                value={formData.negativePrompt}
                onChange={handleChange}
                className="border border-gray-300 p-2 w-full"
              />
            </div>
            <div>
              <label htmlFor="modalStyle">Modal & style:</label>
              <select
                id="modalStyle"
                name="modalStyle"
                value={formData.modalStyle}
                defaultValue="anime"
                onChange={handleChange}
                className="border border-gray-300 p-2 w-full"
              >
                <option value="anime">Anime</option>
                <option value="photographic">Photographic</option>
              </select>
            </div>
            <div>
              <label htmlFor="cfgScale">CFG scale:</label>
              <input
                type="number"
                id="cfgScale"
                name="cfgScale"
                value={formData.cfgScale}
                defaultValue={7}
                onChange={handleChange}
                className="border border-gray-300 p-2 w-full"
              />
            </div>
            <div>
              <label htmlFor="steps">Steps:</label>
              <input
                type="number"
                id="steps"
                name="steps"
                value={formData.steps}
                defaultValue={50}
                onChange={handleChange}
                className="border border-gray-300 p-2 w-full"
              />
            </div>
            <button
              type="submit"
              className="bg-blue-500 text-white px-4 py-2 rounded"
            >
              Generate Image
            </button>
          </form>
        </div>
        <div>
          {loading ? (
            <div className="w-full flex items-center justify-center bg-white h-96 shadow-lg rounded-md ">
              <p className="animate-pulse text-2xl">Loading...</p>
            </div>
          ) : imageURL ? (
            <div className="w-full h-full">
              <img
                src={imageURL}
                alt="Generated Image"
                className="max-h-full max-w-full shadow-lg rounded-md"
              />
            </div>
          ) : (
            <div className="w-full flex items-center justify-center bg-slate-400 h-96 shadow-lg rounded-md ">
              <p className="animate-pulse text-white">No Image generated</p>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

export default ImageForm;

Step 3: Implement ImagePage Component

Now, let’s create the ImagePage the feature that will use the ImageForm component.

// pages/image.js
import ImageForm from "../components/ImageForm";

const ImagePage = () => {
  return (
    <div className="container mx-auto py-9">
      <ImageForm />
    </div>
  );
};

export default ImagePage;

Feel free to add custom styling to your components using CSS or any preferred styling method. Or you can write your own custom CSS.

Remember that this example provides a basic implementation of integrating the Stability API in Next.js. You can further enhance it by adding error handling, handling negative prompts, or incorporating more options provided by the Stability API.

That’s it! You now have a functional text-to-image generator using the Stability API in your Next.js application. If you want to learn about programming you can read our different programming-related articles. Happy coding!

Leave a Comment