AI appNode.js

Next.js AI Chat App

Full-stack AI app: chat UI + a Route Handler that calls the OpenAI Responses API with the key kept server-side.

Open in builder →

7 steps

Shell

Shown with defaults: npm, pip, Node LTS, Python 3.12 and the template's default add-ons.

  1. 1. Install and select Node.js with nvm

    runtime

    nvm lets you install several Node.js versions side by side and switch per project. `nvm use` activates it for this shell.

    bash
    nvm install --ltsnvm use --lts
    Expected result
    `node -v` prints the selected version.
    Verify
    node -v && npm -v
    OS notes
    macOS/Linux/WSL: install nvm with `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash`, then reopen the terminal. Windows: use nvm-windows (github.com/coreybutler/nvm-windows) from an elevated terminal, or fnm.
  2. 2. Scaffold a Next.js app

    template

    create-next-app generates an App Router project. Flags make it non-interactive and reflect your add-on choices (TypeScript, Tailwind, ESLint).

    bash
    npx create-next-app@latest nvx-ai-chat --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes
    Expected result
    A ./nvx-ai-chat folder with src/app, package.json and installed dependencies.
    Verify
    ls nvx-ai-chat/src/app
    OS notes
    Requires Node.js 20.9 or newer. On Windows, run in PowerShell or Windows Terminal.
  3. 3. Enter the project folder

    template

    The scaffolder created ./nvx-ai-chat. Move into it before installing anything else.

    bash
    cd nvx-ai-chat
    Expected result
    You are inside ./nvx-ai-chat
    Verify
    ls package.json
  4. 4. Install the OpenAI SDK

    template

    Used only on the server inside the Route Handler.

    bash
    npm install openai
    Expected result
    openai in dependencies.
    Verify
    npm ls openai
  5. 5. Write the chat API and UI

    template

    The browser posts to /api/chat; the server calls OpenAI. Put your key in .env.local (git-ignored by create-next-app).

    Files written by this step: src/app/api/chat/route.ts, src/app/page.tsx, .env.local
    src/app/api/chat/route.ts
    import OpenAI from "openai";
    
    export async function POST(req: Request) {
      if (!process.env.OPENAI_API_KEY) {
        return Response.json({ error: "Set OPENAI_API_KEY in .env.local" }, { status: 503 });
      }
      const { message } = await req.json();
      const client = new OpenAI();
      try {
        const response = await client.responses.create({
          model: process.env.OPENAI_MODEL || "gpt-6.1-sol",
          instructions: "You are a concise, friendly assistant.",
          input: String(message ?? "").slice(0, 4000),
        });
        return Response.json({ answer: response.output_text });
      } catch (error) {
        const status = error instanceof OpenAI.APIError && error.status ? error.status : 500;
        return Response.json({ error: "OpenAI request failed (" + status + ")" }, { status: 502 });
      }
    }
    
    src/app/page.tsx
    "use client";
    
    import { useState } from "react";
    
    export default function Chat() {
      const [messages, setMessages] = useState<{ role: string; text: string }[]>([]);
      const [input, setInput] = useState("");
      const [busy, setBusy] = useState(false);
    
      async function send() {
        const message = input.trim();
        if (!message || busy) return;
        setInput("");
        setBusy(true);
        setMessages((m) => [...m, { role: "you", text: message }]);
        try {
          const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message }) });
          const data = await res.json();
          setMessages((m) => [...m, { role: "ai", text: data.answer ?? data.error ?? "No answer" }]);
        } finally {
          setBusy(false);
        }
      }
    
      return (
        <main className="mx-auto flex h-screen max-w-2xl flex-col gap-4 p-6">
          <h1 className="text-2xl font-bold">nvx-ai-chat</h1>
          <ul className="flex-1 space-y-3 overflow-y-auto" aria-live="polite">
            {messages.map((m, i) => (
              <li key={i} className={m.role === "you" ? "text-right" : ""}>
                <span className="inline-block rounded-xl border px-3 py-2 whitespace-pre-wrap">{m.text}</span>
              </li>
            ))}
          </ul>
          <form className="flex gap-2" onSubmit={(e) => { e.preventDefault(); send(); }}>
            <label htmlFor="msg" className="sr-only">Message</label>
            <input id="msg" className="flex-1 rounded-xl border px-3 py-2" value={input} onChange={(e) => setInput(e.target.value)} placeholder="Ask anything..." />
            <button className="rounded-xl border px-4 py-2" disabled={busy}>{busy ? "..." : "Send"}</button>
          </form>
        </main>
      );
    }
    
    .env.local
    OPENAI_API_KEY=
    OPENAI_MODEL=gpt-6.1-sol
    
    Expected result
    Route and page files exist.
  6. 6. Start chatting

    templaterun manually · dev server

    Add your key to .env.local, restart, and send a message.

    bash
    npm run dev
    Expected result
    Replies appear in the chat.
    Verify
    curl -s -X POST http://localhost:3000/api/chat -H "Content-Type: application/json" -d "{\"message\":\"hi\"}"
  7. 7. Create a production build

    template

    Make sure everything compiles before deploying.

    bash
    npm run build
    Expected result
    Build succeeds.
    Verify
    ls .next