Skip to content

Running the code locally ​

This page gets an extracted Intro to AI Agents project running on your own computer: an Express server that talks to your AI provider, and a Vite frontend you open in the browser. Nothing later in the course depends on it, and the Scrimba version keeps working whether or not you do this.

What you need first ​

A supported LTS version of Node.js. Node 24 is recommended, and Node 22 also works. Check what you have:

bash
$ node --version
v24.18.0

If that says "command not found", install the version marked LTS from nodejs.org.

npm. Node includes npm, and Scrimba downloads are npm projects. Check that it is available:

bash
$ npm --version
11.18.0
JunoWhat you need first Install the version of Node.js marked LTS from nodejs.org and you get npm with it, so both commands above should print a version number.

If either one says "command not found", that is the whole problem, and installing Node fixes both at once.

JunoWhat you need first Node 24 is the recommendation and Node 22 works. The project loads its .env file with a loader built into Node, and it stops with a message naming Node 20.12 if that loader is missing.

That number explains the message; it is not a version to install. Node 20 no longer gets security updates, so if you manage several Node versions, set this project to 24 and move on.

JunoWhat you need first The generated environment.js imports loadEnvFile from node:process. On a Node without it, the guard throws "Local environment loading requires Node.js 20.12 or newer." instead of carrying on with empty variables.

That matters because the alternative would lie to you: blank variables reach the provider as a missing key, and the symptom would point at your credentials instead of your runtime. Here, that message always means change Node, not .env.

Open the project folder ​

Open a terminal in the extracted folder containing package.json:

bash
$ cd path-to-your-downloaded-project

Have a look at what's there. The exact files vary by lesson. package.json is always there and lists the commands the project expects; the application lessons also include files such as server.js, environment.js, and vite.config.js.

JunoOpen the project folder Use cd to move into the extracted folder that holds package.json. Every command on this page runs from there.

When a command says a file is missing, check which folder your terminal is in before you change anything else.

JunoOpen the project folder Open package.json before you run anything. Its scripts block tells you the commands this project expects, which beats guessing.

Seeing server.js and vite.config.js side by side is worth noticing too: this project runs an Express server and a Vite dev server together, which is how the API key stays off the page.

JunoOpen the project folder The course source uses pnpm, but the Scrimba packaging step removes the pnpm lock and workspace files and rewrites the scripts to call npm, so start becomes concurrently --raw "npm run --silent server" "npm run --silent client".

Work from the downloaded package with npm as it is, instead of recreating the source workspace around it.

Install and run ​

On Scrimba, AI_URL, AI_KEY, and AI_MODEL are stored in your account settings, and Scrimba injects them into the running project. That's why there's no .env file in the browser version: it isn't needed there, and it would be a poor place to keep a key in a shared editor anyway. On your machine nothing injects them, so a .env file does that job.

Install the downloaded project's dependencies, then start it:

bash
$ npm install
$ npm start

On the first run, environment.js notices that the required values are missing, creates .env beside package.json, and stops the Express server with a message that includes these lines:

text
Missing environment variables: AI_KEY, AI_MODEL, AI_URL.
Created .env with the required variable names. Complete it, then restart the app.

Vite may keep running, so press Ctrl+C to stop it before you edit the new file. The generated .env has one empty line per variable and a commented port line at the end:

dotenv
AI_KEY=
AI_MODEL=
AI_URL=
# PORT=3001

Fill in your real values straight after each =, with no spaces around it:

dotenv
AI_KEY=your-api-key-here
AI_MODEL=gpt-5.4-nano
AI_URL=https://api.openai.com/v1
# PORT=3001

These are the same values you put into Scrimba's environment variables. If you need them again, see provider setup. Leave the # PORT=3001 line as it is; the project uses port 3001 unless you change it.

Some later lessons add optional lines such as GITHUB_TOKEN= to the generated file. The server can start without them and prints ○ Optional environment not configured: GITHUB_TOKEN. Without a GitHub token, GitHub requests use the lower anonymous rate limit.

Never commit your .env file

If you put this project in Git, list .env in a .gitignore in the project folder before your first commit. A key pushed to a public repository is a key you have to revoke, and automated scrapers find them within minutes. node_modules belongs there too, because it is large and npm install rebuilds it:

txt
.env
node_modules

.env.example is the file that's safe to commit, because it holds placeholder text rather than your key. The Git handbook covers the wider habit in ignoring files and good habits.

Restart the project after saving .env:

bash
$ npm start

Node loads the file through the generated environment.js. The downloaded vite.config.js calls Vite's loadEnv, so Vite reads the same file when it configures the browser-to-server proxy. You do not need to install dotenv or edit server.js.

npm start runs two processes at once: the Express server that holds your API key and makes model requests, and the Vite dev server that serves the frontend. You'll see output from both interleaved in the same terminal. A ticked environment check and both address lines mean it worked:

text
Environment check:
✓ AI_KEY: configured
✓ AI_MODEL: gpt-5.4-nano
✓ AI_URL: https://api.openai.com/v1
OpenSwap server running at http://localhost:3001

  VITE v6.4.3  ready in 214 ms

  ➜  Local:   http://localhost:5173/

Open the Vite URL, not the Express one. The frontend is what you interact with, and it forwards its API calls through to Express behind the scenes.

Stop both with Ctrl+C.

JunoInstall and run Run npm install once, then npm start. The first start creates .env and stops; press Ctrl+C, fill in the three values you use on Scrimba, save, and start again.

When you see the environment check ticks and the Vite address, open that address. If you use Git, create the .gitignore before your first commit, not after.

JunoInstall and run The one thing that changes off Scrimba is that nothing supplies the environment values, so .env does a job that was invisible before. The generated environment.js creates the file with the right names and loads it for Express.

Vite is a separate process and may survive the first server failure, which is why you stop the whole command with Ctrl+C and restart it after filling in the values.

JunoInstall and run The server loads secrets through Node's built-in loadEnvFile, while Vite calls loadEnv only to configure its proxy. The browser receives no key: it calls relative /api routes, and Express owns the provider request.

If a key does reach a repository, deleting the file in a later commit does not remove it; it stays readable in history. Revoking it at the provider and issuing a new one is the only real fix, which is why the .gitignore comes first.

How the two servers find each other ​

Your frontend code calls paths like /api/swaps, with no hostname and no port. That works because of the proxy in vite.config.js:

js
import { defineConfig, loadEnv } from "vite";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");
  const port = env.PORT || process.env.PORT || 3001;

  return {
    server: {
      hmr: false,
      watch: {
        ignored: ["**/*"],
      },
      proxy: {
        "/api": {
          target: `http://localhost:${port}`,
        },
      },
    },
  };
});

Anything starting with /api gets forwarded to Express. This is why frontend code never needs to know which port the backend is on, and it's also why you don't hit cross-origin errors in development.

The course playgrounds deliberately disable Vite's automatic reload behavior so a save cannot wipe the current output while you're working through a lesson. Refresh the browser manually when you want to load a change.

JunoHow the two servers find each other Use the Vite address, usually http://localhost:5173, in your browser. Calls beginning with /api are passed to the Express server for you, so you never open port 3001 directly.

After you edit a file, refresh the browser yourself; the page does not reload on its own.

JunoHow the two servers find each other Vite's proxy maps relative /api requests to Express on the port from .env, or 3001 when no port is set. That keeps the frontend independent of the backend port and avoids a separate cross-origin setup during development.
JunoHow the two servers find each other Both processes resolve PORT independently from the same .env: Node before Express listens, and Vite while building the proxy target. A commented # PORT line counts as unset, so both fall back to 3001 together.

HMR and file watching are intentionally disabled, so changes need a manual browser refresh.

When port 3001 is taken ​

If something else on your machine already uses port 3001, Express fails to start with EADDRINUSE. Open .env, remove the # from the # PORT=3001 line at the end (or add the line if it isn't there), and change the number:

dotenv
PORT=3101

Then stop the project with Ctrl+C and run npm start again. That is the only change you need. server.js reads PORT to decide where to listen, and Vite reads the same value before configuring its proxy.

The frontend port behaves differently. If 5173 is busy, Vite moves to the next free port and prints that address instead, so read the Local: line rather than typing localhost:5173 from memory. To choose the frontend port yourself, pass it to Vite:

bash
$ npm run client -- --port 5180

The bare -- tells npm to pass the flag after it to Vite instead of treating it as an npm option.

JunoWhen port 3001 is taken If you see EADDRINUSE, open .env, change # PORT=3001 to PORT=3101 (the # goes), and restart. The frontend picks up the new backend port automatically.

For the frontend itself, just open whatever address Vite prints.

JunoWhen port 3001 is taken Express and the Vite proxy both read PORT from the same .env, so changing that one value keeps them in sync.

The Vite frontend has its own port: it walks up from 5173 when that is busy, and npm run client -- --port 5180 sets it directly.

JunoWhen port 3001 is taken Express has no fallback: listen on a taken port throws, so the backend needs an explicit PORT. Vite in this project does not set strictPort, so it moves up on its own.

Backend-port changes belong in .env, frontend-port changes are Vite CLI arguments, and the proxy follows the backend because it reads the same value. Nothing hard-codes a target.

If something doesn't work ​

EADDRINUSE means the port is already in use. See the section above.

Missing environment variables: followed by names means those lines in .env are still empty, or the file was not saved. Fill them in and restart.

A missing-credentials or authentication error means AI_KEY isn't reaching the code, or the provider rejected it. Check that your file is named exactly .env and not .env.txt, that it sits in the same folder as package.json, that the value is filled in, and that you restarted the server after editing it.

"Local environment loading requires Node.js 20.12 or newer." Your Node is too old for the project's built-in .env loader. Install the current LTS release, Node 24, from nodejs.org, then run npm start again. You do not need dotenv.

A model-not-found error usually means AI_MODEL and AI_URL disagree, for instance an OpenAI model ID against OpenRouter's URL. Change all three variables together when you switch providers.

API calls return 404 from the frontend suggests Express isn't running or is on a different port than the proxy expects. Look at your terminal: you should see both the Express line and the Vite line. If only Vite started, Express crashed, and the reason will be just above it.

Install completes but npm start cannot find a package. Run npm install again and include the full output when asking for help. The download includes package-lock.json, so pnpm and a workspace configuration are not involved.

Everything looks right and it still fails. Bring it to the course Discord, with what you ran and what came back. Setup problems are almost always specific to one machine, and someone has usually hit yours already.

JunoIf something doesn't work Start with the exact error text and find it in the list above. Port errors point to a port in use, missing-variable and authentication errors point to .env, and a frontend 404 usually means Express is not running.

Restart with npm start after every change to .env.

JunoIf something doesn't work Treat setup as three checkpoints: dependencies installed, both processes running, and all three AI variables agreeing on one provider and model.

The first checkpoint that fails tells you which layer to look at, so fix that one before touching anything after it.

JunoIf something doesn't work Read failures by boundary: npm resolves packages, environment.js loads credentials, Express owns provider traffic, and Vite proxies browser API requests.

Verify the boundary just before the failing one instead of changing several layers at once.