n4nAI

Calling n4n.ai from Google Cloud Functions in Node.js

Step-by-step tutorial to build a serverless Node.js LLM API on Google Cloud Functions that calls an OpenAI-compatible gateway, with deploy and test code.

n4n Team3 min read567 words

Audio narration

Coming soon — every post will get a voice note here.

A google cloud functions node.js llm api integration lets you expose generative model calls behind a scalable serverless endpoint without running your own inference fleet. This tutorial builds a production-ready HTTP function that proxies requests to an OpenAI-compatible LLM gateway and returns chat completions.

Prerequisites

  • Node.js 18+ (global fetch is required)
  • A Google Cloud project with billing enabled
  • gcloud CLI installed and authenticated (gcloud auth login)
  • An API key from n4n.ai, whose OpenAI-compatible endpoint covers 240+ models and handles provider fallback automatically
  • Familiarity with npm and basic shell commands

No LLM SDK is needed. The gateway speaks the OpenAI Chat Completions shape, so we use plain fetch.

Project setup

Create a directory and initialize a minimal package.

mkdir gcf-llm-proxy && cd gcf-llm-proxy
npm init -y
npm install @google-cloud/functions-framework

Add a start script to package.json for local testing:

{
  "scripts": {
    "start": "functions-framework --target=chat --port=8080"
  }
}

Writing the Cloud Function

Create index.js. The handler accepts a JSON body with message and optional model, calls the gateway, and returns the model response.

const BASE_URL = process.env.LLM_BASE_URL || 'https://api.n4n.ai/v1';
const API_KEY = process.env.LLM_API_KEY;

exports.chat = async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).send('Method Not Allowed');
    return;
  }

  const { message, model = 'gpt-4o-mini' } = req.body || {};
  if (!message || typeof message !== 'string') {
    res.status(400).json({ error: '`message` string required in body' });
    return;
  }

  try {
    const upstream = await fetch(`${BASE_URL}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({
        model,
        messages: [{ role: 'user', content: message }],
        max_tokens: 256,
        temperature: 0.7,
      }),
    });

    const text = await upstream.text();
    if (!upstream.ok) {
      res.status(upstream.status).json({ error: text });
      return;
    }

    const data = JSON.parse(text);
    res.status(200).json({
      model: data.model,
      content: data.choices?.[0]?.message?.content ?? '',
      usage: data.usage,
    });
  } catch (err) {
    res.status(502).json({ error: `Upstream call failed: ${err.message}` });
  }
};

The function strips the response down to fields most clients need. The gateway forwards provider cache-control hints and honors any routing headers you add; for a basic call this is transparent.

Running locally

Store the key in a local env file (do not commit it):

echo 'LLM_API_KEY=sk-your-key-here' > .env.local

Start the framework with the env var:

LLM_API_KEY=$(grep -oP '(?<=LLM_API_KEY=).*' .env.local) npm start

In another terminal, send a request:

curl -X POST localhost:8080 \
  -H 'Content-Type: application/json' \
  -d '{"message":"What is 2+2?","model":"gpt-4o-mini"}'

Expected output:

{
  "model": "gpt-4o-mini",
  "content": "2 + 2 equals 4.",
  "usage": { "prompt_tokens": 12, "completion_tokens": 8, "total_tokens": 20 }
}

If you see a 400, check the request body. A 502 indicates the upstream fetch threw—usually a missing key or network block.

Deploying to Google Cloud Functions

First, put the key in Secret Manager:

gcloud secrets create llm-api-key --replication-policy=automatic
echo -n "sk-your-key-here" | gcloud secrets versions add llm-api-key --data-file=-

Deploy the function with a 60-second timeout (LLM calls can be slow):

gcloud functions deploy chat \
  --runtime nodejs18 \
  --trigger-http \
  --allow-unauthenticated \
  --set-secrets LLM_API_KEY=llm-api-key:latest \
  --timeout 60s \
  --region us-central1

The --allow-unauthenticated flag makes it publicly callable; for real workloads attach Cloud IAM or API Gateway in front.

Deployment output includes a httpsTrigger.url. Copy it.

Testing the deployed function

curl -X POST https://us-central1-your-project.cloudfunctions.net/chat \
  -H 'Content-Type: application/json' \
  -d '{"message":"Name three capitals in Europe."}'

Expected response shape is identical to the local run. Latency will be higher on first call due to cold start; subsequent calls reuse the warmed instance.

Error handling and timeouts

Cloud Functions has a max timeout (currently 9 minutes on Gen2). Set --timeout to match your worst-case model latency. If the gateway returns a 429 or 503, the function passes the status through; the gateway already performs automatic fallback across providers, so a single key outage won’t necessarily fail the request.

For production, wrap the fetch in a small retry with exponential backoff:

async function fetchWithRetry(url, opts, retries = 2) {
  for (let i = 0; i <= retries; i++) {
    const r = await fetch(url, opts);
    if (r.ok || r.status < 500) return r;
    await new Promise(res => setTimeout(res, 2 ** i * 200));
  }
  return await fetch(url, opts);
}

Swap fetch for fetchWithRetry in the handler.

Streaming (optional extension)

The gateway supports SSE streaming on the same endpoint. To stream from the function, set stream: true in the body and pipe the upstream response to res with Content-Type: text/event-stream. Cloud Functions supports streaming responses on Gen2 runtimes; just avoid res.json() and use res.write().

Cost and metering notes

The gateway meters per token; your Cloud Function bill is separate and based on invocation count and duration. Because the function is idle between calls, you pay nothing when traffic is zero. Keep max_tokens bounded to avoid surprise completion lengths, and consider caching repeated prompts at the client layer.

This google cloud functions node.js llm api pattern keeps credentials server-side and gives you a single URL to swap models by changing one field. When you need to add a new model, no redeploy is required—just pass a different model string in the JSON body.

Cleanup

To avoid ongoing charges, delete the function:

gcloud functions delete chat --region us-central1

Remove the secret if no longer needed:

gcloud secrets delete llm-api-key

The google cloud functions node.js llm api wrapper above is deliberately small. From here, add request validation with a schema library, wire IAM, or front it with Cloud Run if you need container-level control.

Tagsgoogle-cloud-functionsnodejsllm-apiserverless

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All google cloud functions & cloud run llm integration posts →