DEV Community

Amany Syori
Amany Syori

Posted on AI-assisted

Build a Tiny Node.js API for Text, Image and Video Generation with the OpenAI SDK

In this tutorial we'll build a small Express server with three endpoints: one for chat, one that generates images and one that starts a video generation job. We'll use the official openai npm package for all of it. The only change from a standard OpenAI setup is the base URL, because we'll route everything through ToAPIs, an OpenAI-compatible AI API gateway with text, image, video and audio models behind one API key and unified billing.

By the end you'll have a backend your frontend can call, with a single secret to manage.

Prerequisites

  • Node.js 18+ (for native fetch and ES modules)
  • A ToAPIs API key. Sign up on the ToAPIs site.
  • Basic Express knowledge

1. Project setup

mkdir ai-gateway-demo && cd ai-gateway-demo
npm init -y
npm pkg set type=module
npm install express openai
export TOAPIS_API_KEY="your-key-here"
Enter fullscreen mode Exit fullscreen mode

2. A shared client

import OpenAI from "openai";

export const BASE_URL = process.env.TOAPIS_BASE_URL ?? "https://toapis.com/v1";

export const client = new OpenAI({
  apiKey: process.env.TOAPIS_API_KEY,
  baseURL: BASE_URL,
});

export const MODELS = {
  text: process.env.TEXT_MODEL ?? "gpt-5.6-terra",
  image: process.env.IMAGE_MODEL ?? "gpt-image-2",
  video: process.env.VIDEO_MODEL ?? "seedance-2",
};
Enter fullscreen mode Exit fullscreen mode

3. Chat endpoint

import express from "express";
import { client, MODELS, BASE_URL } from "./ai.js";

const app = express();
app.use(express.json({ limit: "1mb" }));

app.post("/api/chat", async (req, res, next) => {
  try {
    const { message } = req.body;
    if (!message) return res.status(400).json({ error: "message is required" });
    const completion = await client.chat.completions.create({
      model: MODELS.text,
      messages: [
        { role: "system", content: "You are a concise, helpful assistant." },
        { role: "user", content: message },
      ],
    });
    res.json({ reply: completion.choices[0].message.content });
  } catch (err) {
    next(err);
  }
});

app.post("/api/image", async (req, res, next) => {
  try {
    const { prompt } = req.body;
    if (!prompt) return res.status(400).json({ error: "prompt is required" });
    const result = await client.images.generate({ model: MODELS.image, prompt });
    const item = result.data[0];
    res.json({ url: item.url ?? null, b64_json: item.b64_json ?? null });
  } catch (err) {
    next(err);
  }
});

app.listen(3000, () => console.log("Listening on http://localhost:3000"));
Enter fullscreen mode Exit fullscreen mode

The same account can also call POST /v1/videos/generations with a model such as seedance-2. Docs: https://docs.toapis.com

Why this shape lasts

OpenAI's request schema became the de facto integration contract. If your gateway speaks that contract, existing SDKs, agents, and eval harnesses keep working. You can swap model to try a cheaper or faster option, and billing stays in one place.

Try ToAPIs: https://toapis.com/en · Pricing: https://toapis.com/en/pricing · MCP: https://api.toapis.com/mcp

Top comments (0)