Building with AI · Guide 16 of 35

Integrating AI into a Website

Connect a browser interface to a controlled server boundary, with clear loading, cancellation and failure behavior.

IntermediateAbout 8 min readPractical exercise

Before you start: AI APIs · JavaScript fetch and async/await. Reading time is an estimate; allow extra time for practice.

What you’ll learn

  • Design a small website feature with a clear input, draft output and acceptance criteria.
  • Trace the browser, application server and provider responsibilities in a complete request.
  • Represent pending, failed, complete and outdated results accurately in the interface.
  • Plan checks for accessibility, source fidelity, privacy and operational failure.
On this page

Start with one useful interaction

Imagine a release-note editor with a “Draft announcement” button. The user supplies a few confirmed changes and receives an editable paragraph. The useful outcome is a faithful draft that takes less editing time. A chat window is optional: a focused button can fit the existing workflow better when the task has a clear beginning and end.

Define success before choosing a model. The draft should preserve the audience and feature names, avoid invented dates or prices, and remain unpublished until the user acts. Include an insufficient-information outcome. If the input is only “something changed,” asking for the actual change is more useful than manufacturing a polished announcement. Keep ordinary editing available when the AI service is unavailable.

Separate the three responsibilities

The browser collects the selected text and displays the result. Your application server establishes the user’s identity, checks access, bounds the request and calls the configured provider. The provider returns a candidate generation. Keeping this separation makes access, cost and result handling controllable independently of the model’s response.

Send the smallest necessary input. If the server already stores the release notes, send their identifier and revision instead of trusting an arbitrary client copy. Resolve the authorized content on the server and preserve which revision was summarized. Keep provider credentials out of browser bundles and public build variables. A hidden input field or a prompt asking for secrecy does not protect a secret.

Responsibilities for the announcement feature
LayerOwnsExample check
BrowserInput, pending state, editable previewThe shown draft matches the selected note revision
Application serverIdentity, access, limits, provider adapterThe current user can read this release note
Model providerGeneration under supplied inputsCompletion status and output are inspected by the application

Make the application endpoint explicit

A small contract might accept a note ID, revision and requested tone, then return a request ID, source revision and draft. Validate tone against an allowed set rather than forwarding arbitrary tool definitions or provider configuration from the browser. Reject missing or inaccessible notes before sending their text to the provider; include a useful response for a revision conflict.

The browser must check both the HTTP outcome and the application result. Fetch does not reject its promise merely because the server returns a status such as 404 or 429. Check the response status, parse the documented body safely, and handle an incomplete or invalid generation as a failure. A successful HTTP exchange does not establish that the draft is complete, faithful or ready to publish.

Application contract sketch, not a provider API
POST /api/announcement-drafts
Request: { noteId, revision, tone: "plain" }
Success: { requestId, sourceRevision, draft }
Possible failures: invalid_input, forbidden, revision_conflict,
                   usage_limit, unavailable, invalid_generation

The server supplies trusted instructions and provider credentials.
The response creates a draft only; publishing is a separate action.

This describes an endpoint you must implement in your own backend. It is not a runnable SDK call and does not promise any particular provider response format.

Keep a draft attached to its source revision

Suppose generation starts from revision 1, then the user edits the notes to revision 2 before the result arrives. The old draft may be well written but no longer describes the current notes. Keep the source revision with the request and result; present an outdated-result state instead of quietly inserting it into the current editor.

This dependency-free simulation runs in Node.js or a browser console. It models only the application decision after a response arrives: no timers, network or AI are involved. The assertions show that the current revision is accepted, an outdated revision is not, and a provider failure is not mistaken for an empty successful draft. In a real UI, separately guard concurrent requests and component disposal.

JavaScript: choose the correct draft view
function draftView(currentRevision, result) {
  if (!result || result.kind !== 'complete') {
    return { state: 'error', draft: null };
  }
  if (result.sourceRevision !== currentRevision) {
    return { state: 'outdated', draft: null };
  }
  if (typeof result.draft !== 'string' || !result.draft.trim()) {
    return { state: 'error', draft: null };
  }
  return { state: 'ready', draft: result.draft.trim() };
}
const complete = { kind: 'complete', sourceRevision: 1, draft: 'CSV export is available.' };
const cases = [
  [1, complete, 'ready'],
  [2, complete, 'outdated'],
  [1, { kind: 'failed' }, 'error'],
  [1, { ...complete, draft: ' ' }, 'error'],
];
for (const [revision, result, expected] of cases) {
  if (draftView(revision, result).state !== expected) throw new Error('Wrong draft state');
}
console.log('4 draft-state checks passed');

Expected output: “4 draft-state checks passed”. This protects a source/result association; it does not authenticate users, validate source truth or prove that a backend enforces revision checks. The current revision must come from the editor’s actual source state.

Make every state understandable and accessible

Show when generation is pending, when a draft is complete and when the request failed. Preserve the user’s notes after failure and offer a bounded retry. If streaming, label partial text as provisional; a stopped stream can look like a finished sentence even when important qualifications are missing. Do not automatically publish or replace the user’s own edits as tokens arrive.

Use a real labeled button, a labeled input or textarea, and a visible status message. A polite live region can announce completion or failure without moving keyboard focus; announcing every token can overwhelm a screen reader. Keep the preview readable on narrow screens and allow normal keyboard editing. Render generated content as text by default; rich HTML requires a deliberate sanitization policy and safe link handling.

Bound cost and recover from failures

Limit input size, output size, concurrent work and per-user usage on the server. Set a deadline for provider work and a clear client timeout state. Aborting the browser request may stop local waiting without guaranteeing that the provider stopped computing. Repeating generation can incur another charge, so avoid automatic unbounded retry loops and distinguish a user quota from a temporary provider failure.

Record request ID, duration, result category and usage information sufficient to investigate problems. Avoid unrestricted logs containing release notes, credentials or private drafts. Decide how long drafts and provider-side data should be retained under the actual service arrangement. Keep a switch that disables generation while preserving the ordinary editor, and compare prompt/model changes on saved representative notes before release.

Test the whole interaction, then assess value

Start with fixed local responses to test success, invalid shape, failure, outdated revision and inaccessible notes. Then test the real backend with a safe account and fictional content. Confirm access controls by trying a note belonging to a different test user. Check keyboard operation, status announcements, mobile layout and the ability to keep editing when generation fails.

Evaluate the drafts separately from transport behavior. Use notes with missing dates, conflicting audience statements, no user-visible change and deliberately irrelevant text. Compare every factual clause with the source. Track unsupported claims and useful abstentions as well as latency. A technically reliable endpoint that produces misleading announcements has not met the feature’s purpose; a good demo on one note is not enough evidence to roll out broadly.

Put it into practice

Try it yourself

Design the announcement interaction for these notes: “CSV export is available to existing accounts; no launch date supplied.” Specify the request, success preview, missing-information result, outdated-revision state and retry behavior. Run the local checks and add a null-result case.

Show hint

A successful design preserves the source revision and account scope, invents no date, keeps failures distinct from completed drafts, and requires a separate publish action. null should produce error. The local checks prove view decisions only; list the access, network, accessibility and factual checks still needed in the actual website.

Optional practice

Check your understanding

5 questions to revisit the ideas. No score is saved, and you can retry. This is separate from Skill Quiz; it does not award XP or unlock guides.

1. What is the strongest success criterion for the announcement feature?
2. Fetch receives an HTTP 429 response. What should the browser do?
3. A draft for revision 1 arrives while the editor shows revision 2. What should happen?
4. Which announcement strategy is generally suitable for a completed draft?
5. All network and UI tests pass. What still requires separate evaluation?

Sources & further reading

Primary references for the concepts in this guide. Examples and exercises are illustrative; verify provider-specific behaviour against the version you use.