AI Agent JavaScript Tutorial: Build a Web Agent with Node.js and OpenAI

TutorialsBy Ivern AI Team13 min read

AI Agent JavaScript Tutorial: Build a Web Agent with Node.js and OpenAI

Most AI agent tutorials focus on Python. But if your stack is JavaScript or TypeScript, you can build production-ready agents with Node.js -- and the tooling has caught up.

This tutorial walks you through building a complete AI agent in JavaScript: from a basic chatbot to a tool-using agent with a web interface. You'll use the Vercel AI SDK, which provides the best developer experience for building AI agents in JS/TS.

In this tutorial:

Related tutorials: AI Agent Python Tutorial · Build AI Agent From Scratch · AI Agent API Integration

Setting Up Your Node.js Project

Prerequisites

  • Node.js 18+ (check with node --version)
  • An OpenAI API key
  • npm or pnpm

Step 1: Initialize the Project

mkdir js-agent-tutorial && cd js-agent-tutorial
npm init -y
npm install ai @ai-sdk/openai dotenv zod

Step 2: Configure Environment

Create .env:

OPENAI_API_KEY=sk-your-key-here
npm install -D typescript @types/node
npx tsc --init

Set "module": "ESNext" and "moduleResolution": "bundler" in tsconfig.json.

Building a Basic AI Chatbot

Start with the simplest agent -- a chatbot that can reason:

// chat.ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';
import 'dotenv/config';

async function chat(message: string) {
  const { text } = await generateText({
    model: openai('gpt-4o'),
    system: 'You are a helpful assistant that provides concise, accurate answers.',
    prompt: message,
  });

  return text;
}

const response = await chat('What are the 3 best practices for building AI agents?');
console.log(response);

Run it: npx tsx chat.ts

This works, but it's just a chatbot. Let's make it an agent by adding tools.

Adding Tool Calling

Tools are what turn a chatbot into an agent. The Vercel AI SDK makes tool definition clean with Zod schemas:

// agent.ts
import { openai } from '@ai-sdk/openai';
import { generateText, tool } from 'ai';
import { z } from 'zod';
import 'dotenv/config';

const webSearch = tool({
  description: 'Search the web for current information',
  parameters: z.object({
    query: z.string().describe('The search query'),
  }),
  execute: async ({ query }) => {
    const response = await fetch(
      `https://api.tavily.com/search`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          query,
          api_key: process.env.TAVILY_API_KEY,
          max_results: 3,
        }),
      }
    );
    const data = await response.json();
    return data.results.map((r: any) => `${r.title}: ${r.content}`).join('\n');
  },
});

const calculator = tool({
  description: 'Evaluate a mathematical expression',
  parameters: z.object({
    expression: z.string().describe('A valid math expression like "2 + 2" or "Math.PI * 5"'),
  }),
  execute: async ({ expression }) => {
    try {
      const result = Function(`"use strict"; return (${expression})`)();
      return `Result: ${result}`;
    } catch {
      return 'Error: Invalid expression';
    }
  },
});

const saveFile = tool({
  description: 'Save content to a file',
  parameters: z.object({
    filename: z.string().describe('The filename to save to'),
    content: z.string().describe('The content to write'),
  }),
  execute: async ({ filename, content }) => {
    const fs = await import('fs/promises');
    await fs.writeFile(filename, content, 'utf-8');
    return `Saved to ${filename}`;
  },
});

Get AI agent tips in your inbox

Multi-agent workflows, BYOK tips, and product updates. No spam.

Running the Agent with Tools

async function runAgent(userMessage: string) {
  const { text, steps } = await generateText({
    model: openai('gpt-4o'),
    system: `You are a research agent. Use tools to find information and complete tasks.
    Always verify facts with web search before stating them.
    Save important results to files.`,
    prompt: userMessage,
    tools: { webSearch, calculator, saveFile },
    maxSteps: 10,
  });

  console.log('Agent output:', text);
  console.log(`Used ${steps.length} steps`);
  return text;
}

await runAgent(
  'Search for the current population of Tokyo, calculate the density per square km (area is 2,194 sq km), and save a summary to tokyo-stats.txt'
);

The agent will:

  1. Search for Tokyo's population
  2. Calculate density using the calculator tool
  3. Save the summary to a file
  4. Report the results

Building a Web Interface

Server Side (Next.js API Route)

// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'You are a helpful AI agent with access to tools. Use them to complete tasks.',
    messages,
    tools: {
      webSearch: tool({
        description: 'Search the web for current information',
        parameters: z.object({ query: z.string() }),
        execute: async ({ query }) => {
          const res = await fetch('https://api.tavily.com/search', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              query,
              api_key: process.env.TAVILY_API_KEY,
              max_results: 3,
            }),
          });
          const data = await res.json();
          return data.results.map((r: any) => `${r.title}: ${r.content}`).join('\n');
        },
      }),
    },
  });

  return result.toDataStreamResponse();
}

Client Side (React Component)

// app/chat/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div className="max-w-2xl mx-auto p-4">
      <h1>AI Agent Chat</h1>
      
      <div className="space-y-4 mb-4">
        {messages.map((m) => (
          <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
            <div className={`inline-block p-3 rounded-lg ${
              m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'
            }`}>
              {m.content}
            </div>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask me anything..."
          className="flex-1 p-2 border rounded"
        />
        <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
          Send
        </button>
      </form>
    </div>
  );
}

Streaming Responses

For long-running agent tasks, streaming keeps users engaged. The Vercel AI SDK handles this natively:

import { streamText } from 'ai';

const result = streamText({
  model: openai('gpt-4o'),
  prompt: 'Research the top 5 AI agent frameworks and compare them',
  tools: { webSearch },
  maxSteps: 10,
  onStepFinish({ toolResults }) {
    console.log('Step completed:', toolResults);
  },
});

for await (const textPart of result.textStream) {
  process.stdout.write(textPart);
}

Production Considerations

Error Handling

const result = await generateText({
  model: openai('gpt-4o'),
  prompt: userMessage,
  tools: { webSearch, calculator, saveFile },
  maxSteps: 10,
  onError: ({ error }) => {
    console.error('Agent error:', error);
  },
});

Rate Limiting

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '1 m'),
});

export async function POST(req: Request) {
  const ip = req.headers.get('x-forwarded-for') ?? 'anonymous';
  const { success } = await ratelimit.limit(ip);
  
  if (!success) {
    return new Response('Rate limited', { status: 429 });
  }
  
  // ... agent logic
}

Cost Tracking

const { text, usage } = await generateText({
  model: openai('gpt-4o'),
  prompt: message,
  tools,
});

console.log(`Tokens: ${usage.promptTokens} prompt + ${usage.completionTokens} completion`);
const cost = (usage.promptTokens * 0.0000025) + (usage.completionTokens * 0.00001);
console.log(`Estimated cost: $${cost.toFixed(4)}`);

For detailed cost analysis, use our AI Cost Calculator.

When to Use JavaScript vs Python for AI Agents

Scroll to see full table

FactorJavaScript/TypeScriptPython
Best forWeb apps, real-time UI, Next.js projectsData science, ML pipelines, research
SDK qualityVercel AI SDK (excellent)LangChain (mature, complex)
Tool ecosystemGrowing fastExtensive
StreamingNative, easyPossible but verbose
Type safetyTypeScript (excellent)Type hints (good)

Choose JavaScript if your project is already in JS/TS or you need tight web integration. Choose Python for research-heavy workflows or when you need Python's ML ecosystem.

For a deeper comparison of frameworks, see our AI Agent Orchestration Guide.

Skip the Code: Use Ivern AI

If you want agent capabilities without building from scratch, Ivern AI gives you:

  • Agent squads in 2 minutes -- no code, no configuration files
  • BYOK pricing -- bring your OpenAI or Anthropic key, zero markup
  • Built-in web interface -- chat with agents, assign tasks, review results
  • Streaming responses -- real-time output as agents work
  • Tool integrations -- web search, file operations, and more built in

Get started free: ivern.ai/signup

Next Steps

  1. Add more tools -- database queries, email sending, Slack notifications
  2. Build multi-agent systems -- see our Multi-Agent Tutorial
  3. Add persistence -- store conversations and agent state in a database
  4. Deploy -- use Vercel, Railway, or your own infrastructure

Related guides: How to Connect Multiple AI Models · AI Agent API Integration · Best AI Agent Platforms 2026

Want to try multi-agent AI for free?

Generate a blog post, Twitter thread, LinkedIn post, and newsletter from one prompt. No signup required.

Try the Free Demo

AI Content Factory -- Free to Start

One prompt generates blog posts, social media, and emails. Free tier, BYOK, zero markup.

No spam. Unsubscribe anytime.