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" 

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", }; 

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")); 

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