Running Embeddings and Vector Databases locally
Use this page to run the extracted ReelRecs movie chatbot from Embeddings and Vector Databases on your computer, with your own OpenAI key and the Supabase database you built during the course. Three things differ from Scrimba's editor: two packages must be installed by name, the reads in config.js need Vite's syntax, and the chat model the project names has been retired.
What you need first
Install a supported LTS version of Node.js. Node 24 is recommended and includes npm.
Check that both commands work. Your version numbers may be different:
$ node --version
v24.18.0
$ npm --version
11.18.0You also need three values:
- An OpenAI API key. Create one you will delete when you are done: this project calls OpenAI straight from the browser, so the key ends up in the page.
- Your Supabase project URL, from the Supabase project that holds the movies and the
match_moviesfunction you made during the course. - That project's publishable key, which starts with
sb_publishable_. If your project shows only the olderanonkey, use that. Never use the secret key or theservice_rolekey here.
node --version and npm --version both print a version. Then collect your OpenAI key, your Supabase project URL and its publishable key. The Supabase project must be the one you filled with movies in the course.
match_movies. If that Supabase project is empty, the chatbot runs but answers "Sorry, I don't know the answer." Install the packages
Open a terminal in the extracted folder. It is the one containing package.json, config.js and index.js:
$ cd path-to-your-extracted-projectpackage.json lists only Vite, but config.js imports the OpenAI and Supabase packages too. Scrimba's editor installs those for you; on your computer, name them and npm installs them along with Vite:
$ npm install openai @supabase/supabase-js
added 25 packages, and audited 26 packages in 9snpm finishes by reporting how many packages it added.
package.json, then run npm install openai @supabase/supabase-js. npm ends by saying how many packages it added. If npm says it cannot find package.json, you are in the wrong folder.
package.json lists, and naming two packages adds them to that list, so one command installs all three. A plain npm install would give you Vite alone. npm start still serves the page: Vite's start-up scan only prints a warning, and the import fails when the browser asks for config.js, as an error over the page. Change config.js and index.js
Scrimba's editor fills in process.env.OPENAI_API_KEY, process.env.SUPABASE_API_KEY and process.env.SUPABASE_URL for you. The browser has no process, and Vite does not create one, so all four reads in config.js must change; the OpenAI key is read twice. Vite hands browser code only the variables whose names start with VITE_, which is why each name gains that prefix.
Replace the contents of config.js with:
import OpenAI from 'openai';
import { createClient } from "@supabase/supabase-js";
/** OpenAI config */
if (!import.meta.env.VITE_OPENAI_API_KEY) throw new Error("OpenAI API key is missing or invalid.");
export const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true
});
/** Supabase config */
const privateKey = import.meta.env.VITE_SUPABASE_API_KEY;
if (!privateKey) throw new Error(`Expected env var SUPABASE_API_KEY`);
const url = import.meta.env.VITE_SUPABASE_URL;
if (!url) throw new Error(`Expected env var SUPABASE_URL`);
export const supabase = createClient(url, privateKey);Only the four reads differ from the file you downloaded. The two Supabase error messages still name the old variables; leave them as they are.
Then open index.js. The chat request asks for gpt-4, which OpenAI has retired. In getChatCompletion, change:
model: 'gpt-4',to:
model: 'gpt-4o-mini',Leave the other model in index.js, text-embedding-ada-002, as it is. The movies in your database were turned into numbers with that model, and your question has to be turned into numbers the same way to be compared with them.
config.js over the old one, then change gpt-4 to gpt-4o-mini in index.js. Don't touch text-embedding-ada-002; your database depends on it.
config.js, it replaces each import.meta.env.VITE_... read with the value from .env. A name without the prefix stays hidden from browser code, arrives as undefined, and trips the checks in config.js. config.js throws while it loads, before index.js attaches its form handler. So a bad read leaves a page that looks normal, but pressing Enter reloads it and your question vanishes. Add your keys to .env
Create .env beside package.json with your three values:
VITE_OPENAI_API_KEY=your-openai-api-key
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_API_KEY=your-publishable-keyCreate .gitignore beside package.json and add:
.env
node_modules/A key that reaches a Git repository stays in its history even after you delete the file. The Git handbook covers the habit in ignoring files and good habits.
These keys are visible in the browser
Vite writes every VITE_ value into the code it sends to the browser, so anyone who opens this page can read your OpenAI key. Keep this version on your own computer: never deploy or share it, and never commit .env. A version you publish needs a backend that keeps the OpenAI key on the server, as in the Vercel AI SDK project.
.env next to package.json with your three values, one per line, then list .env in .gitignore so Git never saves it. .env when it starts and then watches it: saving a change prints .env changed, restarting server..., and the open page reloads with the new values. VITE_ prefix is an exposure allowlist, not secret storage: the OpenAI key sits in the module Vite serves and in the header of every request the browser sends to OpenAI. Start the chatbot and ask a question
Start the development server:
$ npm startVite may first print a warning that begins (!) Your Vite config uses features. It does not stop the app. A different warning, (!) Failed to run dependency scan, means the two packages are missing: go back to the install step. Below the warnings, Vite prints the address to open:
VITE v8.3.4 ready in 218 ms
➜ Local: http://localhost:5173/Open the exact Local address. The page shows ReelRecs and a box that says "How can I help?". Ask about a movie in your database and press Enter. Thinking... appears, and after a few seconds the answer replaces it. An answer about your movies means OpenAI and Supabase both work.
If your question disappears and nothing else happens, the page never got your keys; the first troubleshooting entries below match the terminal line.
If the page says "Sorry, something went wrong. Please try again.", look in the terminal where npm start is running. Vite copies the browser's error there, on a line containing Error in main function.; the troubleshooting below matches each message. The same line is in your browser's developer tools (F12, or Cmd+Option+J in Chrome on a Mac), on the Console tab.
Stop the project with Ctrl+C. If you save .env while it runs, Vite restarts itself (.env changed, restarting server...) and the page reloads with the new values.
npm start, ignore the config warning if it appears, and open the Local address. Ask about one of your movies and wait for the answer. If you get "Sorry, something went wrong", read the newest line in the terminal.
match_movies finds the nearest movies, and OpenAI writes the reply. Any of them failing shows the same sorry message, so the error line tells you which one to fix. main wraps all three requests in one try/catch and logs only the error's message. Vite 8 forwards that console.error from the browser to the terminal, which is why the terminal shows a browser failure. Troubleshooting
The terminal shows (!) Failed to run dependency scan, and the browser shows Failed to resolve import "openai" from "config.js": The two packages are not installed. Stop the project, run npm install openai @supabase/supabase-js in the folder that contains package.json, then start the project again.
Your question vanishes when you press Enter, and the terminal shows ReferenceError: process is not defined: A process.env read is still in config.js. Replace the file with the version shown above and save it.
Your question vanishes, and the terminal shows OpenAI API key is missing or invalid or Expected env var: Vite found no value. Check that .env is beside package.json and that each name starts with VITE_, then save it; Vite restarts itself and the page reloads. The messages name the old variables, but the names in .env are the VITE_ ones.
Error in main function. 401 Incorrect API key provided: OpenAI did not accept the key. Copy it again from your OpenAI account and check that it has not been deleted.
Error in main function. 429 You exceeded your current quota: The key works, but the account has no credit to spend. Add credit under Billing in your OpenAI account, then ask again.
Error in main function. Cannot read properties of null (reading 'map'): OpenAI worked, but Supabase sent back an error instead of movies. Check the project URL, use the publishable key, and check that match_movies exists in that same project. For the exact reason, open the developer tools' Network tab, ask again, and click the request named match_movies.
The chatbot answers "Sorry, I don't know the answer.": Everything ran, but Supabase found no movie close enough to your question. Ask about a movie that is in your table. If every question gets this answer, check in Supabase that the table has rows and that Row Level Security lets the publishable key read them.
Error in main function. followed by a message that names the model (gpt-4 or gpt-4o-mini): Check that index.js asks for gpt-4o-mini, not gpt-4. After January 14, 2027, OpenAI stops serving gpt-4o-mini too; from then on, use the replacement its deprecations page names.
Error in main function. and match the words after it to a bold heading above. If your question just vanishes, look at config.js and .env first. config.js, the OpenAI key, then Supabase, then your data. Fix the first one you meet; the next only shows once it is gone. findNearestMatch ignores the error that Supabase returns and calls data.map on the null beside it. So a wrong URL, a rejected key and a missing function all read as the same null; the Network tab holds the real reason. 
