Skip to main content

Intro


This tutorial walks through building an AI-powered book recommender with React, TypeScript, Ant Design, and Domo’s AI Service Layer. You’ll learn how to:
  • Scaffold a Vite + React + TypeScript app with the DA CLI
  • Search a public API (Open Library) with debounced queries as the user types
  • Call Domo’s AI text generation endpoint with a structured system prompt and parse the JSON response
  • Build a polished Ant Design form and results view
The finished code is at DomoApps/book-recommender-tutorial on GitHub.
Prerequisite: Complete the Setup and Installation guide and run domo login before starting. Your Domo instance needs access to the AI Service Layer.

Step 1: Install the DA CLI and scaffold the app


The DA CLI clones the @domoinc/vite-react-template — a Vite + React + TypeScript project preconfigured with the Domo proxy, ESLint, Prettier, Vitest, Storybook, and da generate scaffolding. Install the CLI globally:
Create the project:
da new prompts for a package manager (pick pnpm), clones the template, writes your app name, initializes git, and installs dependencies.
App names must be lowercase with hyphens only. Capitals, underscores, and periods are rejected.
Add the runtime dependencies we’ll use beyond the scaffold:
  • Ant Design (antd) — the UI component library we’ll use for the select inputs and buttons.
  • ryuu.js — the Domo JS client for calling Domo platform APIs (the AI Service Layer in this case) through the dev-server proxy.

Step 2: Add static assets


Drop the background image and divider graphic into public/static/:
  • bookshelf.jpeg — full-bleed background
  • chapter_divider.png — decorative divider under the heading
You can grab both from the sample repo’s public/static/, or swap in your own. Vite serves anything in public/static/ at the /static/ path at runtime.

Step 3: Publish the initial design and wire the proxy


Calls to Domo APIs (including the AI Service Layer) go through the dev-server proxy, which needs a proxyId to authorize requests. That means we need to publish a skeleton, create a proxy card, and paste the IDs back into manifest.json. First, set the app metadata. Replace public/manifest.json with:
Then upload the skeleton:
This runs pnpm build and domo publish from the build/ folder. The output prints a link to the new App Design. In the Domo UI:
  1. Open the App Design link — or go to MoreAsset Library and find your design.
  2. Click New Card. This app doesn’t use a dataset, so you can save the card without selecting one — we just need it to generate a proxyId.
  3. Save the card.
Copy the App Design id and the card’s proxyId from the design page, and add both to public/manifest.json:
For multi-environment apps, keep manifest.json clean and use da manifest to add overrides to src/manifestOverrides.json instead of hand-editing.

Step 4: Build the App component


The whole app lives in one component: src/components/App/App.tsx. It has three responsibilities:
  1. Search Open Library as the user types — debounced, so we don’t hammer the API.
  2. Collect preferences — favorite books (multi-select), genre, mood, and length.
  3. Submit to Domo’s AI service and render a grid of recommendations.
The scaffold created App.tsx already — replace its contents with:
The full genre / mood / length lists in the sample repo are longer — keep or trim them to taste. State and derived values. Inside export const App: FC = () => {:
matchingBooks is what the dropdown currently shows; allBooks is a running union of every book we’ve fetched so far, so when the user selects one from an old search we can still resolve it by key. Debounced Open Library search.
The 300 ms debounce keeps Open Library happy and means the dropdown only updates when the user pauses. fetch here hits Open Library directly — it’s a public API, no Domo proxy involved.

Step 5: Call the Domo AI service


Domo’s AI Service Layer exposes /domo/ai/v1/text/generation for text generation. We pass three things:
  • input — the structured user query
  • promptTemplate.template — wraps the input (the ${input} placeholder is replaced server-side)
  • system — the system prompt that forces JSON-only output
The model returns a choices[0].output string that we strip of any code-fence wrapping and parse as JSON.
The regex strip handles the case where the model still wraps output in ```json … ``` despite the system prompt telling it not to — cheaper than re-prompting.

Step 6: Render the form and results


Still inside App:
The two branches of the ternary — form vs. results — keep the component simple: once we have recommendations, swap the view and offer Edit preferences (clear results, show the form again) or Try again (re-submit the same preferences). Styling. The SCSS is short — grab it from App.module.scss in the sample repo or write your own.

Step 7: Test locally


The Vite dev server starts on port 3000 (or 3001/3002 if busy). Because proxyId is set, domo.post('/domo/ai/v1/text/generation', ...) is authenticated through the proxy to your real Domo instance — you should see real recommendations come back in a few seconds.
Warning: If /domo/ai/v1/text/generation returns 401 or 403, your user or instance doesn’t have access to the AI Service Layer. Reach out to your Domo admin. If it returns 404, check that proxyId in manifest.json matches the card you created in Step 3.

Step 8: Publish


The new build becomes the active design. Anyone instantiating the app from the Asset Library picks it up.

Next steps


  • Stream results incrementally by swapping /domo/ai/v1/text/generation for the streaming variant and parsing partial chunks.
  • Persist favorite-book lists per user with an AppDB collection (see the Todo App tutorial).
  • Swap Open Library for Google Books or any other search API — the debounce pattern is the same.
  • Continue with Mapbox World Map or Todo App with AppDB.