Step-by-Step Tutorial to Convert ComfyUI Workflow into API with an Actual Example in Next.js

Step-by-Step Tutorial to Convert ComfyUI Workflow into API with an Actual Example in Next.js

Step-by-Step Tutorial to Convert ComfyUI Workflow into API with an Actual Example in Next.js

ComfyUI is an advanced graphical user interface for building intricate image generation workflows, particularly in AI and machine learning. While it excels in visual experimentation, integrating these workflows into applications requires an API-based approach. This tutorial provides a step-by-step guide to converting your ComfyUI workflow into a functional API using Next.js, complete with actual code examples.

Table of Contents

  1. Prerequisites
  2. Understanding the Workflow
  3. Setting Up the Next.js Application
  4. Creating API Routes
  5. Establishing WebSocket Connections
  6. Modifying the Workflow Dynamically
  7. Tracking Progress
  8. Retrieving and Serving Images
  9. Putting It All Together
  10. Conclusion

Prerequisites

Before we begin, ensure you have the following:

  • Node.js (v12 or higher) and npm installed.
  • Next.js project set up. If not, we'll cover how to create one.
  • ComfyUI installed and running on your machine.
  • Basic understanding of JavaScript/TypeScript and Next.js.
  • Familiarity with RESTful APIs and WebSockets.

Understanding the Workflow

A ComfyUI workflow consists of interconnected nodes that define the image generation process. Each node performs a specific function, such as applying a filter or generating noise. By converting this workflow into an API, you enable external applications to interact with it programmatically.

Setting Up the Next.js Application

Initialize a New Next.js Project

First, create a new Next.js project. In your terminal, run:

npx create-next-app comfyui-api-tutorial

Navigate to the project directory:

cd comfyui-api-tutorial

Install Required Dependencies

We'll need additional packages for WebSocket communication and handling HTTP requests:

npm install ws uuid
  • ws: A WebSocket library for Node.js to handle WebSocket connections.
  • uuid: For generating unique client IDs.

Creating API Routes

Next.js provides a convenient way to create API routes that run on the server side.

API Route to Queue a Prompt

Create a new file at pages/api/queuePrompt.js:

// pages/api/queuePrompt.js
 
import { v4 as uuidv4 } from 'uuid';
 
export default async function handler(req, res) {
  if (req.method === 'POST') {
    const serverAddress = '127.0.0.1:8188';
    const { prompt } = req.body;
    const clientId = uuidv4();
 
    const payload = {
      prompt,
      client_id: clientId,
    };
 
    try {
      const response = await fetch(`http://${serverAddress}/prompt`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
 
      if (!response.ok) {
        throw new Error(`Error queueing prompt: ${response.statusText}`);
      }
 
      const data = await response.json();
 
      res.status(200).json({ promptId: data.prompt_id, clientId });
    } catch (error) {
      console.error('Error:', error);
      res.status(500).json({ error: error.message });
    }
  } else {
    res.setHeader('Allow', 'POST');
    res.status(405).end('Method Not Allowed');
  }
}

API Route to Retrieve History

Create a new file at pages/api/history/[promptId].js:

// pages/api/history/[promptId].js
 
export default async function handler(req, res) {
  const { promptId } = req.query;
  const serverAddress = '127.0.0.1:8188';
 
  try {
    const response = await fetch(`http://${serverAddress}/history/${promptId}`);
 
    if (!response.ok) {
      throw new Error(`Error retrieving history: ${response.statusText}`);
    }
 
    const data = await response.json();
 
    res.status(200).json(data);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: error.message });
  }
}

Establishing WebSocket Connections

To receive real-time updates from ComfyUI, we need to establish a WebSocket connection.

WebSocket Utility

Create a new directory utils and a file websocket.js:

// utils/websocket.js
 
import WebSocket from 'ws';
import { v4 as uuidv4 } from 'uuid';
 
export function openWebSocketConnection() {
  const serverAddress = '127.0.0.1:8188';
  const clientId = uuidv4();
 
  const ws = new WebSocket(`ws://${serverAddress}/ws?clientId=${clientId}`);
 
  ws.on('open', () => {
    console.log('WebSocket connection established.');
  });
 
  ws.on('close', () => {
    console.log('WebSocket connection closed.');
  });
 
  ws.on('error', (error) => {
    console.error('WebSocket error:', error);
  });
 
  return { ws, clientId };
}

Modifying the Workflow Dynamically

We need to adjust the workflow based on the user's input.

Workflow Utility

Create a new file utils/workflow.js:

// utils/workflow.js
 
import crypto from 'crypto';
 
export function modifyWorkflow(workflowJson, positivePrompt, negativePrompt = '') {
  const workflow = JSON.parse(workflowJson);
 
  // Map node IDs to class types
  const idToClassType = {};
  Object.entries(workflow).forEach(([id, node]) => {
    idToClassType[id] = node.class_type;
  });
 
  // Find the KSampler node
  const kSamplerNodeId = Object.keys(idToClassType).find(
    (id) => idToClassType[id] === 'KSampler'
  );
 
  if (kSamplerNodeId) {
    const kSamplerNode = workflow[kSamplerNodeId];
 
    // Set a random seed
    kSamplerNode.inputs.seed = crypto.randomInt(1e14, 1e15 - 1);
 
    // Update positive prompt
    const positiveInputId = kSamplerNode.inputs.positive[0];
    workflow[positiveInputId].inputs.text = positivePrompt;
 
    // Update negative prompt if provided
    if (negativePrompt) {
      const negativeInputId = kSamplerNode.inputs.negative[0];
      workflow[negativeInputId].inputs.text = negativePrompt;
    }
  }
 
  return JSON.stringify(workflow);
}

Tracking Progress

We can listen to WebSocket messages to track the progress of the image generation.

Progress Utility

Update utils/websocket.js:

// utils/websocket.js
 
// ... existing code ...
 
export function trackProgress(ws, promptId, onProgressCallback, onCompleteCallback) {
  ws.on('message', (data) => {
    const message = JSON.parse(data);
 
    if (message.type === 'progress' && message.data.prompt_id === promptId) {
      const progress = message.data;
      onProgressCallback(progress);
    }
 
    if (
      (message.type === 'execution_complete' ||
        message.type === 'processing_complete') &&
      message.data.prompt_id === promptId
    ) {
      ws.close();
      onCompleteCallback();
    }
  });
}

Retrieving and Serving Images

After the image generation is complete, retrieve the images from ComfyUI.

Image Retrieval Utility

Create a new file utils/images.js:

// utils/images.js
 
export async function getGeneratedImages(serverAddress, promptId) {
  const response = await fetch(`http://${serverAddress}/history/${promptId}`);
 
  if (!response.ok) {
    throw new Error(`Error retrieving history: ${response.statusText}`);
  }
 
  const history = await response.json();
 
  const promptHistory = history[promptId];
  const images = [];
 
  for (const nodeOutput of Object.values(promptHistory.outputs)) {
    if (nodeOutput.images) {
      images.push(...nodeOutput.images);
    }
  }
 
  return images;
}

API Route to Serve Images

Create a new file at pages/api/images/[promptId].js:

// pages/api/images/[promptId].js
 
import { getGeneratedImages } from '../../../utils/images';
 
export default async function handler(req, res) {
  const { promptId } = req.query;
  const serverAddress = '127.0.0.1:8188';
 
  try {
    const images = await getGeneratedImages(serverAddress, promptId);
 
    res.status(200).json({ images });
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: error.message });
  }
}

Putting It All Together

Now, let's integrate all the pieces in a single API route that handles the entire flow.

Complete API Route

Create a new file pages/api/generateImage.js:

// pages/api/generateImage.js
 
import { openWebSocketConnection, trackProgress } from '../../utils/websocket';
import { modifyWorkflow } from '../../utils/workflow';
import { getGeneratedImages } from '../../utils/images';
 
export default async function handler(req, res) {
  if (req.method === 'POST') {
    const serverAddress = '127.0.0.1:8188';
    const { positivePrompt, negativePrompt } = req.body;
 
    try {
      const { ws, clientId } = openWebSocketConnection();
 
      // Load your workflow JSON string
      const workflowJson = getYourWorkflowJson();
 
      // Modify the workflow with user prompts
      const modifiedWorkflow = modifyWorkflow(
        workflowJson,
        positivePrompt,
        negativePrompt
      );
 
      // Queue the modified prompt
      const promptResponse = await fetch(`http://${serverAddress}/prompt`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt: modifiedWorkflow, client_id: clientId }),
      });
 
      if (!promptResponse.ok) {
        throw new Error(`Error queueing prompt: ${promptResponse.statusText}`);
      }
 
      const promptData = await promptResponse.json();
      const promptId = promptData.prompt_id;
 
      // Track progress
      trackProgress(
        ws,
        promptId,
        (progress) => {
          console.log(`Progress: ${progress.value} / ${progress.max}`);
        },
        async () => {
          // Image generation completed
          const images = await getGeneratedImages(serverAddress, promptId);
          res.status(200).json({ images });
        }
      );
    } catch (error) {
      console.error('Error:', error);
      res.status(500).json({ error: error.message });
    }
  } else {
    res.setHeader('Allow', 'POST');
    res.status(405).end('Method Not Allowed');
  }
}
 
// Dummy function to simulate retrieving your workflow JSON
function getYourWorkflowJson() {
  // Replace this with actual code to retrieve your workflow
  // For example, read from a file or database
  return JSON.stringify({
    // ... your ComfyUI workflow JSON ...
  });
}

Client-Side Usage

You can create a simple page to interact with the API.

// pages/index.js
 
import { useState } from 'react';
 
export default function Home() {
  const [positivePrompt, setPositivePrompt] = useState('');
  const [negativePrompt, setNegativePrompt] = useState('');
  const [images, setImages] = useState([]);
 
  const generateImage = async () => {
    const response = await fetch('/api/generateImage', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ positivePrompt, negativePrompt }),
    });
 
    if (response.ok) {
      const data = await response.json();
      setImages(data.images);
    } else {
      console.error('Error generating image:', response.statusText);
    }
  };
 
  return (
    <div>
      <h1>ComfyUI Image Generator</h1>
      <input
        type="text"
        placeholder="Positive Prompt"
        value={positivePrompt}
        onChange={(e) => setPositivePrompt(e.target.value)}
      />
      <input
        type="text"
        placeholder="Negative Prompt"
        value={negativePrompt}
        onChange={(e) => setNegativePrompt(e.target.value)}
      />
      <button onClick={generateImage}>Generate Image</button>
      <div>
        {images.map((image, index) => (
          <img
            key={index}
            src={`data:image/png;base64,${image.base64}`}
            alt={`Generated ${index}`}
          />
        ))}
      </div>
    </div>
  );
}

Or, You Can Use ComfyUIAsAPI.com's Service

If you don't want to deal with the hassle of setting up the API, you can use ComfyUIAsAPI.com's service.

All you need is to upload your ComfyUI workflow .json file and get a ready-to-use API. We also support SDKs for all the popular languages. You just need a few lines of code to integrate it into your project.

Conclusion

By following this step-by-step tutorial, you've transformed your ComfyUI workflow into a functional API using Next.js. This integration allows for seamless interaction between your image generation workflow and other applications, enabling dynamic input and real-time feedback. Leveraging Next.js API routes and server-side capabilities provides a robust platform for building interactive and responsive AI-powered applications.

Ready to get started?

Join the waitlist today.

We’re gradually rolling out access to the waitlist, so sign up early to get the first chance to try ComfyUIAsAPI.com. Join now to stay ahead of the curve and help shape the future of the ComfyUI ecosystem!