Skip to content

Running the Vercel AI SDK course locally

Use this page to run the extracted customer-support agent on your computer. You connect its Express server to OpenAI and the Supabase data created in the course, then make one start-script change so Node loads those values from .env. The Embeddings and Vector Databases guide runs a related project against the same kind of Supabase vector setup.

What you need first

Install a supported LTS version of Node.js. Node 24 is recommended. You also need:

  • an OpenAI API key with billing and model access;
  • the Supabase project and data created during the course;
  • a key with server-side database access: either a Supabase secret key or the legacy service_role key.

A Supabase secret key and the legacy service_role key both bypass Row Level Security. A key of either type belongs only in this server-side .env, never in browser code. See Supabase API keys.

JunoWhat you need first Install Node.js LTS, then gather your OpenAI key plus the Supabase project and the Supabase secret key from the course. A Supabase secret key works like a master key to the whole database, so it never goes anywhere near browser code.
JunoWhat you need first The server reads Supabase with a key that has elevated permissions, and retrieval returns nothing useful without the rows created during the course. Both services and that data have to exist before your first run. Keep the Supabase secret key in the Express environment only; it never belongs in the browser.
JunoWhat you need first A Supabase secret key and the legacy service_role key both bypass Row Level Security, so whoever holds one acts with the server's full database permissions. Keep that key in Express only; sending it to the browser gives the same access to every visitor who opens devtools. I have seen that mistake reach production one time, and rotating the key afterward took the rest of the day.

Open and install the project

Open a terminal in the extracted folder containing package.json, then install the locked packages:

bash
$ cd path-to-your-downloaded-project
$ npm ci
JunoOpen and install the project Run npm ci in the extracted folder, the one containing package.json. It installs exactly the package versions the course was built with, so there is nothing to configure or guess here.
JunoOpen and install the projectnpm ci installs from the included lockfile, so your versions match the extracted customer-support project exactly. That match matters because the SDK and model code were tested together; a dependency version that has drifted costs you a debugging session you did not plan for.
JunoOpen and install the project Install from the downloaded lockfile before touching anything else. Then change only the start script, so an unrelated dependency update cannot be mistaken for the environment-file fix; keeping those two changes separate has saved me many evenings.

Make Node load .env

Open package.json and change the start script from:

json
"start": "node server.js"

to:

json
"start": "node --env-file=.env server.js"

The code uses the legacy variable name SUPABASE_SERVICE_ROLE_KEY. You can store a current Supabase secret key in that variable without renaming anything in the code. The value has to be a Supabase secret key or the legacy service_role key, not a publishable key.

Create .env beside package.json:

dotenv
OPENAI_API_KEY=your-openai-api-key
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-server-side-secret-key
PORT=3000

Create .gitignore:

txt
.env
node_modules/

The Git handbook covers this habit in ignoring files and good habits.

Keep the Supabase key on the server

Never rename the Supabase secret key with a VITE_ prefix or move it into client.js. That key has elevated database access. Do not commit .env or share it in a ZIP.

JunoMake Node load .env Add --env-file=.env to the start script, create the file with all four values, and keep it out of Git. The Supabase secret key stays on the server, always. A .gitignore with .env in it is the first file I create in any project, a habit I formed only after committing a key once.
JunoMake Node load .env Node does not read .env on its own, so the extracted project starts with no configuration. The --env-file flag fixes that using a feature built into Node: no extra package, and the values stay on the server where Express reads them.
JunoMake Node load .env The built-in flag loads the four existing variable names before server.js runs. Because only Express reads them, the OpenAI and Supabase secrets never appear in code sent to the browser. No renaming, no dotenv dependency, no new attack surface.

Keep the supplied models

The downloaded constants.js uses gpt-4o for answer generation and classification and text-embedding-3-small for embeddings. Local setup does not require changing either model. Keep them unchanged unless OpenAI rejects one for your project. If you later replace a model, test the complete agent flow and confirm that new query embeddings remain compatible with the stored Supabase vectors. Replacing the embedding model can fail without any error message: a model with the same dimension count but a different vector space returns poor matches and reports no error, so regenerate the stored vectors after changing the embedding model.

JunoKeep the supplied models Leave both supplied model names unchanged for local setup; they are not the reason anything fails on this page. If your OpenAI project rejects one, check the current model list before editing the code, and retest the whole agent afterward.
JunoKeep the supplied models The answer, classification, and embedding roles have different compatibility needs, so replacing a model is never a one-line change. A replacement has to work with this SDK and stay compatible with the vectors already stored in Supabase.
JunoKeep the supplied models An embedding model with the same dimension count but a different vector space breaks retrieval without any warning: queries return poor matches and no error at all. That is why replacing the embedding model means regenerating the stored vectors, not only editing constants.js. This is the failure mode I watch for most carefully, because the answers get worse and nothing tells you.

Run the agent

bash
$ npm start

Open http://localhost:3000, or use the port you set in .env. Stop the server with Ctrl+C.

The page loading shows that the local server is running. Ask a question covered by the course data: a useful answer shows that Supabase retrieval and OpenAI generation are working too. If an answer fails, the server terminal prints the first failing call in the chain: embedding, retrieval, or generation. The RAG chapter explains why the agent bases its answers on retrieved course data.

JunoRun the agent Run npm start and open the local address in your browser. The page loading proves the server runs; a full answer also needs OpenAI and the course's Supabase data, so treat those as two separate checks.
JunoRun the agent Treat server startup and answer generation as separate checks: a running server with failing answers is a service problem, not a code problem. Stop with Ctrl+C and set PORT in .env if another process is already using the default port.
JunoRun the agent When an answer fails, the server terminal prints the first failing call in the chain: embedding, retrieval, or generation. Read that error and identify which call failed before you change any configuration. Guessing at configuration when three external calls are involved is how an evening disappears; I speak from experience.

Troubleshooting

Missing OPENAI_API_KEY: Confirm that the start script includes --env-file=.env, that .env is beside package.json, and that the variable name matches exactly.

Supabase authentication or relation error: Confirm the URL and the Supabase secret key belong to the same project, then complete the course's schema and data steps. This server-side operation needs a Supabase secret key; a publishable key, which is meant for browser code, does not have the access it requires.

Port 3000 is already in use: Change PORT in .env, restart the server, and open the new port.

The page loads but answers fail: Check the server terminal for the first provider or database error. Local startup alone does not validate either external service.

JunoTroubleshooting A missing key points to the start script or .env, a Supabase error points to the project or its data, and a loaded page with failed answers points to an external service. Match the symptom to the layer first, and the fix is usually clear from there.
JunoTroubleshooting Read the first server-terminal error, not the last one; the later failures are usually consequences of it. That first error separates environment loading, database authorization, port binding, and OpenAI access into four distinct fixes.
JunoTroubleshooting Check the server errors in order: whether Node loaded .env, whether Supabase returned the course data, and whether OpenAI generated an answer. And never replace the Supabase secret key with a browser-safe publishable key to make an error go away, because that hides a missing table or policy instead of fixing it.