Add a chatbot with Express + Vite
Goal: the same floating assistant as the Next.js guide, on a site with an Express backend and a Vite + React front end.
- .env
- vite.config.ts proxies /api to Express in development
Directorysrc
- server.ts Express: /api/a2a (+ /config) and /api/nebulas-surfaces
- main.tsx
- App.tsx renders the chat
- index.css Tailwind + tokens
-
Install the dependencies.
Terminal window npm install express dotenv react react-dom \@gnomondigital/nebulas-kit-core @gnomondigital/nebulas-kit-reactnpm install -D vite @vitejs/plugin-react tailwindcss@3 postcss autoprefixer \typescript @types/express @types/react @types/react-domThen set up Tailwind as described in Installation.
-
Write the Express server. Import from
/express(or the package root) so Next.js is never loaded.src/server.ts import "dotenv/config";import express, { type Router } from "express";import { configureNebulasServer } from "@gnomondigital/nebulas-kit-core";import {createA2AExpressRouter,createNebulasSurfaceRouter,} from "@gnomondigital/nebulas-kit-core/express";const app = express();app.use(express.json({ limit: "2mb" })); // allows room for file uploadsconfigureNebulasServer({a2aUrl: process.env.A2A_URL,nebulasApiUrl: process.env.NEBULAS_API_URL,nebulasApiKey: process.env.NEBULAS_API_KEY,nebulasProjectId: process.env.NEBULAS_PROJECT_ID,nebulasAuthStrategy: "resource_owner_password",authProvider: "auth0",auth0: {domain: process.env.AUTH0_DOMAIN ?? "",clientId: process.env.AUTH0_CLIENT_ID ?? "",clientSecret: process.env.AUTH0_CLIENT_SECRET ?? "",audience: process.env.AUTH0_A2A_AUDIENCE,},auth0ServiceUser: {username: process.env.AUTH0_ROP_USERNAME ?? "",password: process.env.AUTH0_ROP_PASSWORD ?? "",},});// Chat: POST /api/a2a and GET /api/a2a/configapp.use("/api/a2a",createA2AExpressRouter(express, {strategy: "resource_owner_password",apiKey: process.env.NEBULAS_API_KEY,}) as Router);// Optional: forms and other widgets (see the Contact form guide)app.use("/api/nebulas-surfaces",createNebulasSurfaceRouter(express, {apiKey: process.env.NEBULAS_API_KEY,}) as Router);app.listen(3001, () => console.log("API on http://localhost:3001")); -
Proxy
/apiin development so the browser can call the API on the same origin:vite.config.ts import { defineConfig } from "vite";import react from "@vitejs/plugin-react";export default defineConfig({plugins: [react()],server: {port: 5173,proxy: { "/api": { target: "http://localhost:3001", changeOrigin: true } },},}); -
Render the chat.
src/App.tsx import { A2AChatFloating } from "@gnomondigital/nebulas-kit-react";export default function App() {return (<><main className="container mx-auto max-w-2xl py-16 px-4"><h1 className="text-2xl font-semibold">Welcome</h1></main><A2AChatFloatinga2aEndpoint="/api/a2a"configEndpoint="/api/a2a/config"panelTitle="Assistant"fabClassName="bg-primary text-primary-foreground hover:bg-primary/90"suggestedPrompts={["What can you help me with?"]}/></>);} -
Run both processes.
Terminal window node --watch --experimental-strip-types src/server.ts & npx viteOpen http://localhost:5173.
In production
Section titled “In production”Build the front end with vite build. Have Express serve the output as static
files, and send every request that isn’t /api/* to index.html:
import path from "path";
app.use(express.static(path.join(__dirname, "client")));app.get("*", (req, res, next) => { if (req.path.startsWith("/api")) return next(); res.sendFile(path.join(__dirname, "client", "index.html"));});The React code then calls /api/... on the same origin, so you don’t need CORS.
