🤖 Beginner  ·  API Integration

Building
AI Apps

Build 3 real AI-powered web applications using the Claude API, pure HTML/CSS/JS, and GitHub Pages. No frameworks. No servers. Just ship.

10 Lessons
100 Questions
Pass: 60%
🏆 Certificate
~5 hrs
0 of 10 lessons passed 0%
🤖 Module 1 — Foundations (Lessons 1–3)
01
Course Overview and Setup
What we build, tools needed, GitHub Pages, Anthropic API intro
🔓

What You Will Build

In this course you will build 3 real AI-powered web applications and deploy them live on GitHub Pages using the Anthropic Claude API:

  1. AI Chat Assistant — A custom chatbot with a system prompt and conversation history
  2. AI Text Analyser — Sentiment analysis, summarisation, and key phrase extraction
  3. AI Study Buddy — A quiz generator that creates questions from any topic you provide

What You Need

  • Basic HTML, CSS, and JavaScript knowledge
  • A GitHub account (free at github.com)
  • An Anthropic API key (from console.anthropic.com)
  • A text editor: VS Code, Acode (Android), or use GitHub Codespaces (browser-based)

Tech Stack — Pure HTML/CSS/JS

We use no frameworks, no Node.js, no build tools. Everything runs in the browser and deploys to GitHub Pages for free. This is intentional — the simpler the stack, the faster you focus on the AI logic itself.

How GitHub Pages Works

GitHub Pages is a free hosting service for static HTML/CSS/JS websites. Create a repository, push your files, enable Pages in settings, and your site is live at yourusername.github.io/repo-name. No servers, no costs, no configuration.

// Glossary Anthropic API — The service that gives your app access to Claude AI
API key — Your secret credential that authenticates your requests
GitHub Pages — Free hosting for static HTML websites from a GitHub repository
Static site — A website made of HTML/CSS/JS files with no server-side code
📋
// Topic Test · Lesson 1
Lesson 1 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1How many AI apps will you build in this course?
1
2
3
5
Q2What hosting platform is used for deployment?
Heroku
Vercel
GitHub Pages
Netlify
Q3What is required to use the Anthropic Claude API?
A paid Anthropic subscription only
An API key from console.anthropic.com
A server running Node.js
A Google account
Q4Why does this course use pure HTML/CSS/JS with no frameworks?
Frameworks are too expensive
Simplicity — no build tools means faster focus on AI logic and immediate deployment
Frameworks do not work with AI
Pure JS is always better
Q5What is GitHub Pages?
A paid hosting service
A documentation platform
Free hosting for static HTML/CSS/JS websites from a GitHub repository
A code review service
Q6What is a static site?
A site that never changes
A website made of HTML/CSS/JS files with no server-side processing
A site with no JavaScript
A read-only website
Q7What is an API key used for?
Encrypting your code
Authenticating your application's requests to the AI service
Paying for API usage
Accessing GitHub
Q8Where do you get an Anthropic API key?
github.com
anthropic.com/api
console.anthropic.com
developer.anthropic.com
Q9What URL format does GitHub Pages use?
yoursite.github.com
github.com/username/site
yourusername.github.io/repo-name
pages.github.com/username
Q10What is the AI Study Buddy app?
A chat assistant
A quiz generator that creates questions from any topic you provide
A code debugger
A flashcard app
0/10 answered
02
Calling the Claude API
fetch(), JSON, CORS, API structure, handling responses
🔒

Making Your First API Call

The Anthropic API uses standard HTTP requests. From a browser, you use the fetch() function with your API key in the headers and your prompt in the request body.

async function askClaude(question) {
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": "YOUR_API_KEY",
      "anthropic-version": "2023-06-01"
    },
    body: JSON.stringify({
      model: "claude-opus-4-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: question }]
    })
  });
  const data = await response.json();
  return data.content[0].text;
}

Understanding the Response

{
  "content": [{ "type": "text", "text": "Claude's reply here" }],
  "model": "claude-opus-4-5",
  "usage": { "input_tokens": 15, "output_tokens": 127 }
}

Error Handling

try {
  const result = await askClaude("Hello!");
  document.getElementById("output").textContent = result;
} catch (error) {
  console.error("API Error:", error);
  document.getElementById("output").textContent = "Error: " + error.message;
}

CORS Warning

Calling the Anthropic API directly from the browser exposes your API key in the JavaScript source code. For a production app, you should route requests through a server or Cloudflare Worker. For learning and personal projects, direct browser calls are acceptable.

// Glossary fetch() — JavaScript function for making HTTP requests
async/await — Syntax for handling asynchronous operations cleanly
JSON — JavaScript Object Notation: the data format the API uses
CORS — Cross-Origin Resource Sharing: browser security policy for API calls
📋
// Topic Test · Lesson 2
Lesson 2 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What JavaScript function is used to make HTTP requests to the API?
XMLHttpRequest
axios
fetch()
request()
Q2What is the Anthropic API endpoint URL?
https://claude.anthropic.com/v1/messages
https://api.anthropic.com/v1/messages
https://ai.anthropic.com/messages
https://api.claude.ai/v1/chat
Q3Which header passes your API key?
Authorization
API-Key
x-api-key
Bearer-Token
Q4What does async/await do?
Makes code run faster
Handles asynchronous operations so you can write async code that reads like synchronous code
Is required for API calls
Encrypts the request
Q5Where is the AI response text in the API response object?
response.text
data.message
data.content[0].text
data.reply
Q6What is JSON?
JavaScript Object Notation — the data format used to send and receive data in API calls
A type of database
A JavaScript framework
An API protocol
Q7What is CORS?
A type of API key
A CSS framework
Browser security policy that controls which domains can make requests to external APIs
A JavaScript error type
Q8Why is calling the API directly from the browser a security concern?
The browser blocks all API calls
The API key is visible in the JavaScript source code which is accessible to anyone
The API does not allow browser calls
It is slower
Q9What does max_tokens control?
How many requests you can make
The cost per request
The maximum length of Claude's response
The model version
Q10What does the try/catch block do in the API call?
Makes the request faster
Catches errors and handles them gracefully instead of crashing the app
Is required by the API
Validates the API key
0/10 answered
03
Building the Chat Interface
HTML structure, CSS styling, sending messages, displaying responses
🔒

The HTML Structure

<div class="chat-container">
  <div class="chat-messages" id="messages"></div>
  <div class="chat-input-row">
    <textarea id="user-input" placeholder="Type a message..."></textarea>
    <button onclick="sendMessage()">Send</button>
  </div>
</div>

JavaScript: Sending and Displaying Messages

const messages = [];

async function sendMessage() {
  const input = document.getElementById("user-input");
  const text = input.value.trim();
  if (!text) return;

  // Add user message to UI
  addMessageToUI("user", text);
  
  // Add to conversation history
  messages.push({ role: "user", content: text });
  input.value = "";

  // Get AI response
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: { "Content-Type": "application/json",
               "x-api-key": API_KEY, "anthropic-version": "2023-06-01" },
    body: JSON.stringify({ model: "claude-opus-4-5", max_tokens: 1024,
                           messages: messages })
  });
  const data = await response.json();
  const reply = data.content[0].text;

  // Add AI response to UI and history
  addMessageToUI("assistant", reply);
  messages.push({ role: "assistant", content: reply });
}

function addMessageToUI(role, text) {
  const msg = document.createElement("div");
  msg.className = "message " + role;
  msg.textContent = text;
  document.getElementById("messages").appendChild(msg);
}
// Glossary Conversation history — The array of messages sent with each API call to maintain context
DOM manipulation — Adding removing or changing HTML elements with JavaScript
Event handler — A function that runs when a user action (click, keypress) occurs
trim() — Removes whitespace from the start and end of a string
📋
// Topic Test · Lesson 3
Lesson 3 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1Why is conversation history (the messages array) sent with every API call?
To make requests larger
The API has no memory — each call is independent so history must be included to maintain context
To track token usage
The API requires it
Q2What does addMessageToUI do?
Sends the message to the API
Creates and adds a new HTML div element for the message into the chat container
Validates user input
Updates the message count
Q3What does trim() do to user input?
Capitalises text
Removes whitespace from the start and end preventing empty messages from being sent
Limits message length
Translates text
Q4What does document.createElement('div') do?
Finds an existing div in the page
Creates a new HTML element in memory ready to be added to the page
Deletes a div
Styles a div
Q5What happens if you send messages to the API without conversation history?
The API returns an error
It gets faster
The AI has no context of previous turns — each response treats it as a new conversation
Nothing changes
Q6What does appendChild() do?
Removes a child element
Finds an element by ID
Adds an element as the last child of another element in the DOM
Creates an element
Q7What is the role field in each message object?
The user's name
Either user or assistant — tells the API who sent each message
The message timestamp
The session ID
Q8What does if (!text) return; prevent?
API key exposure
Sending empty or whitespace-only messages to the API
Duplicate messages
Long messages
Q9How do you capture Enter key press to send a message?
It happens automatically
Add a keydown event listener checking if event.key === 'Enter'
The textarea handles it
Use a form onsubmit
Q10What does class='message user' vs class='message assistant' allow?
Different fonts
Identifying message senders programmatically and styling them differently with CSS
Different languages
Different API endpoints
0/10 answered
04
System Prompts and Personas
Custom AI personas, restrictions, knowledge injection, tone control
🔒

What is a System Prompt?

A system prompt is a set of instructions you send before the conversation starts that defines how your AI should behave. Users do not see it — it runs silently in the background, shaping every response.

Adding a System Prompt to Your Chat

const response = await fetch("https://api.anthropic.com/v1/messages", {
  // ... headers ...
  body: JSON.stringify({
    model: "claude-opus-4-5",
    max_tokens: 1024,
    system: `You are Sola, a friendly learning assistant for SWAL Learn.
    
Your rules:
- Only help with topics related to technology, programming, and learning
- Always encourage students when they struggle
- Use simple language appropriate for beginners
- When answering code questions, always include a working example
- If asked about unrelated topics, gently redirect to tech learning`,
    messages: messages
  })
});

Persona Examples

  • Customer Support Bot — "You are a support agent for PayFast. Only answer questions about our payment products. For refunds escalate to human@payfast.com"
  • Code Reviewer — "You are a senior developer. Review code for: security vulnerabilities, performance issues, and style. Format findings as Critical/Major/Minor."
  • Study Partner — "You are a patient tutor. Never give direct answers. Use the Socratic method — ask guiding questions that lead students to discover answers themselves."
// Glossary System prompt — Instructions that define AI behaviour before conversation starts
Persona — The AI identity and personality defined in the system prompt
Socratic method — Teaching through questions that guide students to discover answers
Escalation — Routing a request to a human agent when AI cannot handle it
📋
// Topic Test · Lesson 4
Lesson 4 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1Where does a system prompt appear in the API request?
Inside the messages array
As a separate system field alongside the messages array
In the request headers
As the first user message
Q2Do users see the system prompt in a normal chat interface?
Yes always
Only on request
No — it runs silently defining AI behaviour without being visible to users
Only in developer mode
Q3What does a system prompt define?
The user interface design
The AI's persona rules capabilities restrictions and tone for the entire conversation
The API billing rate
The response length
Q4What is the Socratic method in AI tutoring?
Giving direct answers quickly
Teaching through guiding questions that lead students to discover answers themselves
A type of prompt injection
Memorisation flashcards
Q5How do you restrict an AI to only answer about specific topics?
You cannot restrict AI
Add explicit rules in the system prompt: Only answer questions about [topic]. Redirect all others.
Use a different model
Filter responses after the API call
Q6What does escalation mean in a customer support bot?
Making the bot respond faster
Routing a conversation to a human agent when the AI cannot resolve the issue
Increasing the max_tokens limit
Adding more system prompt rules
Q7Why would you inject company documentation into a system prompt?
To increase response speed
To give the AI accurate specific knowledge about your product it would not have from training
Required by the API
To reduce token usage
Q8What is the persona of an AI?
Its programming language
The identity name personality and communication style defined in the system prompt
The model version
The response format
Q9What is a safe restriction to add to any customer-facing AI?
Never answer any questions
Never make specific financial legal or medical recommendations — always recommend consulting a professional
Only answer in one language
Limit responses to 50 words
Q10What is the benefit of a Code Reviewer persona?
Faster code execution
Structured consistent code feedback categorised by severity — more actionable than generic comments
Automatic bug fixes
Syntax highlighting
0/10 answered
05
Streaming Responses
Server-sent events, streaming UX, displaying text as it generates
🔒

What is Streaming?

By default, the API waits until Claude has finished generating the complete response before sending it to you. With streaming, text is sent as it is generated — token by token — creating a typewriter effect that makes responses feel instant rather than making users wait.

Enabling Streaming in the API

const response = await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": API_KEY,
    "anthropic-version": "2023-06-01"
  },
  body: JSON.stringify({
    model: "claude-opus-4-5",
    max_tokens: 1024,
    stream: true,          // Enable streaming
    messages: messages
  })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = "";

const messageDiv = document.createElement("div");
messageDiv.className = "message assistant";
document.getElementById("messages").appendChild(messageDiv);

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  
  const chunk = decoder.decode(value);
  const lines = chunk.split("\n");
  
  for (const line of lines) {
    if (line.startsWith("data: ")) {
      const json = line.slice(6);
      if (json === "[DONE]") continue;
      try {
        const parsed = JSON.parse(json);
        if (parsed.type === "content_block_delta") {
          fullText += parsed.delta.text;
          messageDiv.textContent = fullText;
        }
      } catch (e) {}
    }
  }
}
// Glossary Streaming — Receiving and displaying text as it is generated rather than waiting for completion
Server-Sent Events (SSE) — Protocol for pushing data from server to client incrementally
TextDecoder — Converts raw binary data (Uint8Array) to readable text
ReadableStream — A JavaScript API for processing data that arrives in chunks
📋
// Topic Test · Lesson 5
Lesson 5 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What problem does streaming solve?
API cost
Users having to wait for the entire response before seeing any text — streaming shows text as it generates
Memory usage
API rate limits
Q2How do you enable streaming in the Anthropic API request?
Add streaming: enabled in headers
Add stream: true to the request body
Set model to claude-stream
Use a different API endpoint
Q3What is response.body.getReader() used for?
Parsing JSON
Reading the response headers
Accessing the streaming response body as a ReadableStream to read chunks as they arrive
Closing the connection
Q4What does TextDecoder do?
Encodes text to binary
Converts raw binary Uint8Array chunks from the stream into readable text strings
Decodes JSON
Validates encoding
Q5What does stream: true change about the response?
Makes it faster
Changes format from a single JSON response to a stream of Server-Sent Event data chunks
Changes the model used
Increases token limit
Q6What does content_block_delta type indicate in a streaming event?
An error occurred
The stream has ended
A chunk of new text to append to the current message
A new message has started
Q7What user experience benefit does streaming provide?
Reduces API costs
Saves bandwidth
Makes responses feel instantly responsive rather than making users stare at a loading state
Changes response quality
Q8What should you do when a streaming chunk throws a JSON parse error?
Stop the stream
Show an error to the user
Catch and ignore the error — non-JSON lines like empty lines are expected in the SSE protocol
Restart the request
Q9When does the streaming loop end?
After a timeout
When reader.read() returns done: true indicating the stream has finished
After 1000 chunks
When the model finishes one sentence
Q10What must you track during streaming to have the full response?
Number of tokens
Response time
The accumulated full text by concatenating each delta chunk as it arrives
Message IDs
0/10 answered
06
The AI Text Analyser
Building the second app: sentiment, summary, key phrases
🔒

App 2: AI Text Analyser

In this lesson you build a tool that analyses any text and returns three insights: overall sentiment, a brief summary, and the key phrases. All from a single API call with a carefully designed prompt.

The Power Prompt

const analyseText = async (text) => {
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": API_KEY,
      "anthropic-version": "2023-06-01"
    },
    body: JSON.stringify({
      model: "claude-opus-4-5",
      max_tokens: 512,
      messages: [{
        role: "user",
        content: `Analyse the following text. Return ONLY a valid JSON object 
        with no additional text, markdown, or code fences:
        {
          "sentiment": "positive" or "negative" or "neutral" or "mixed",
          "sentiment_score": a number from -1.0 (very negative) to 1.0 (very positive),
          "summary": "One sentence summary of the main point",
          "key_phrases": ["phrase1", "phrase2", "phrase3"],
          "tone": "formal" or "casual" or "emotional" or "technical"
        }
        
        Text to analyse: "${text}"`
      }]
    })
  });
  const data = await response.json();
  return JSON.parse(data.content[0].text);
};

Displaying Results

const result = await analyseText(userText);
sentimentEl.textContent = result.sentiment;
scoreEl.textContent = result.sentiment_score.toFixed(2);
summaryEl.textContent = result.summary;
result.key_phrases.forEach(phrase => {
  const tag = document.createElement("span");
  tag.className = "phrase-tag";
  tag.textContent = phrase;
  phrasesEl.appendChild(tag);
});
// Glossary Sentiment analysis — Determining whether text expresses positive negative or neutral feelings
JSON.parse() — Converts a JSON string to a JavaScript object
toFixed(2) — Formats a number to exactly 2 decimal places as a string
Key phrase extraction — Identifying the most important terms and concepts in text
📋
// Topic Test · Lesson 6
Lesson 6 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What three things does the Text Analyser extract from text?
Words sentences and paragraphs
Sentiment summary and key phrases
Author date and topic
Length complexity and reading time
Q2Why does the prompt say Return ONLY valid JSON with no additional text?
JSON is the only language the API understands
So the output can be directly passed to JSON.parse() without stripping surrounding prose
To reduce token usage
To make the prompt shorter
Q3What is JSON.parse() used for?
Creating JSON from an object
Converting a JSON string from the API response into a usable JavaScript object
Validating JSON
Sending JSON in a request
Q4What does sentiment_score: -1.0 to 1.0 represent?
An error code
The confidence of the classification
A numerical scale where -1 is very negative 0 is neutral and 1 is very positive
The number of negative words
Q5What is key phrase extraction?
Pressing keys on a keyboard
Extracting the most important terms and concepts from the text
Finding all nouns
Counting words
Q6What does toFixed(2) do to a number?
Rounds to nearest integer
Multiplies by 100
Formats as a string with exactly 2 decimal places
Removes decimal places
Q7What would happen if the AI returns JSON with markdown code fences despite the instruction?
Nothing — it works fine
JSON.parse() would throw an error because the string would contain extra characters like the backticks
The app ignores extra text
The browser handles it automatically
Q8How do you add a try/catch around JSON.parse()?
You cannot
try { const result = JSON.parse(text); } catch(e) { // handle malformed JSON }
JSON.parse always succeeds
Use JSON.tryParse() instead
Q9What tone values does the prompt ask the AI to classify?
happy sad angry neutral
positive negative neutral
formal casual emotional technical
assertive passive aggressive neutral
Q10Why send all three analyses (sentiment summary key phrases) in one API call?
The API requires bundling
Reduces latency and API costs — three separate calls would triple the time and cost
The API cannot handle separate calls
One call always returns better results
0/10 answered
07
The AI Study Buddy
Building the quiz generator: topic input, question generation, scoring
🔒

App 3: AI Study Buddy

The Study Buddy generates a quiz on any topic in seconds. The user enters a topic and number of questions, the AI generates a structured quiz, and the app handles scoring and feedback.

The Quiz Generator Prompt

const generateQuiz = async (topic, numQuestions) => {
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: { "Content-Type": "application/json",
               "x-api-key": API_KEY, "anthropic-version": "2023-06-01" },
    body: JSON.stringify({
      model: "claude-opus-4-5",
      max_tokens: 2048,
      messages: [{
        role: "user",
        content: `Generate a quiz about "${topic}" with ${numQuestions} questions.
Return ONLY a valid JSON array with no additional text:
[{
  "question": "The question text",
  "options": ["Option A", "Option B", "Option C", "Option D"],
  "correct": 0,
  "explanation": "Why this answer is correct"
}]
Each question must have exactly 4 options. correct is the 0-based index.`
      }]
    })
  });
  const data = await response.json();
  return JSON.parse(data.content[0].text);
};

Rendering and Scoring

function renderQuiz(questions) {
  questions.forEach((q, index) => {
    const qDiv = document.createElement("div");
    qDiv.className = "question";
    qDiv.innerHTML = `<p>${index + 1}. ${q.question}</p>`;
    q.options.forEach((opt, i) => {
      const btn = document.createElement("button");
      btn.textContent = opt;
      btn.onclick = () => checkAnswer(btn, i, q.correct, q.explanation, qDiv);
      qDiv.appendChild(btn);
    });
    quizContainer.appendChild(qDiv);
  });
}
// Glossary 0-based index — Arrays in JavaScript start counting from 0, not 1
Dynamic rendering — Creating HTML elements with JavaScript rather than hard-coding them
Template literal — A string using backticks that allows embedded expressions with ${}
Event delegation — Attaching click handlers to dynamically created elements
📋
// Topic Test · Lesson 7
Lesson 7 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What does the quiz generator prompt ask the AI to return?
A paragraph describing the topic
A JSON array of question objects with question options correct index and explanation
HTML for the quiz page
A numbered list of questions
Q2What does correct: 0 mean in the quiz JSON?
Zero questions are correct
The question is wrong
The first option (index 0) is the correct answer
The question has no correct answer
Q3Why must correct be a 0-based index rather than 1-based?
It is easier to read
JavaScript arrays are 0-indexed so options[0] is the first option
The API requires it
Human convention prefers 0-based
Q4What does dynamic rendering mean in this context?
Rendering 3D graphics
Creating quiz HTML elements from the generated JSON data rather than hard-coding them in the HTML
Making the page load faster
Using CSS animations
Q5Why include an explanation field in each quiz question?
To make the JSON larger
To provide immediate educational feedback after the user answers — telling them why the answer is correct
It is required by the API
For accessibility
Q6What is a template literal in JavaScript?
A design pattern
A string using backticks that allows embedding variables and expressions directly with ${}
An HTML template
A type of comment
Q7How do you attach a click handler to a dynamically created button?
It is not possible
btn.onclick = () => handleClick()
Use addEventListener only
Buttons auto-click
Q8What is the risk of using innerHTML to display quiz question text?
Makes page slower
If the AI generates text with < or > characters it could break the HTML or create XSS vulnerabilities
innerHTML is not supported
It uses more memory
Q9Why use max_tokens: 2048 for the quiz generator vs 512 for text analysis?
Cost reasons
Generating multiple structured questions requires more output tokens than a short analysis response
Different models have different defaults
2048 is the default
Q10What happens if the AI returns invalid JSON despite the instruction?
Nothing — invalid JSON is ignored
The app crashes silently
JSON.parse() throws a SyntaxError which should be caught and shown as an error to the user
The API catches it
0/10 answered
08
Error Handling and Rate Limits
HTTP errors, rate limiting, retry logic, user feedback
🔒

What Can Go Wrong

Production AI apps encounter many types of errors. Handling them gracefully is what separates a professional app from a broken prototype.

HTTP Error Codes

  • 401 Unauthorized — Invalid or missing API key
  • 400 Bad Request — Invalid request format or parameters
  • 429 Too Many Requests — Rate limit exceeded. Slow down!
  • 500 Internal Server Error — Problem on Anthropic's side
  • 529 Overloaded — Anthropic servers are overloaded. Retry with backoff.

Robust Error Handling

async function callAPI(messages, retries = 3) {
  for (let attempt = 0; attempt < retries; attempt++) {
    try {
      const response = await fetch("https://api.anthropic.com/v1/messages", {
        // ... config ...
      });
      
      if (!response.ok) {
        const error = await response.json();
        if (response.status === 429 || response.status === 529) {
          // Wait before retrying: 1s, 2s, 4s (exponential backoff)
          await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
          continue;
        }
        throw new Error(`API Error ${response.status}: ${error.error?.message}`);
      }
      
      return await response.json();
    } catch (error) {
      if (attempt === retries - 1) throw error;
    }
  }
}
// Glossary Rate limiting — API restriction on how many requests you can make per minute
Exponential backoff — Waiting progressively longer between retries (1s, 2s, 4s, 8s...)
HTTP status code — A 3-digit number indicating the result of an HTTP request
Retry logic — Automatically re-attempting a failed request with a delay
📋
// Topic Test · Lesson 8
Lesson 8 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What HTTP status code indicates your API key is invalid?
400
404
401
403
Q2What does a 429 status code mean?
Not found
Server error
Rate limit exceeded — you are sending too many requests
Your account is suspended
Q3What is exponential backoff?
Reducing request size exponentially
Waiting progressively longer between retries: 1s then 2s then 4s then 8s etc.
Sending exponentially more requests
A type of data compression
Q4Why retry on 429 and 529 but not on 401?
The numbers are different
401 means your API key is wrong and retrying will not help. 429/529 are temporary and can resolve with waiting.
Retrying is always wrong
The API documentation says so
Q5What does response.ok mean?
The response body is valid
The HTTP status code is in the 200-299 range indicating success
The JSON is valid
The API key is valid
Q6What is user feedback in error handling?
Sending error reports
Showing clear meaningful error messages to users instead of crashing silently or showing technical stack traces
Rating the AI response
Collecting user opinions
Q7What is a loading state in an AI app?
The initial page load
Visual feedback shown while waiting for the API response preventing the user from thinking the app is frozen
A type of error state
The streaming state
Q8Why disable the send button while waiting for an API response?
API requirement
Prevents users from sending duplicate requests while one is already in progress
Saves API calls
Prevents errors
Q9What does Math.pow(2, attempt) produce for attempts 0 1 2?
0 1 2
1 2 3
1 2 4 — exponential growth for backoff timing
2 4 8
Q10What should you do when all retry attempts are exhausted?
Silently fail
Keep retrying forever
Throw the error so it can be caught by the caller and shown as a user-friendly message
Reset the app
0/10 answered
09
Improving UX — Loading States and Feedback
Spinners, disabled states, typing indicators, copy buttons
🔒

Professional UX for AI Apps

The difference between a toy project and a professional AI app is often not the AI — it is the user experience around the AI. Loading states, typing indicators, copy buttons, and clear error messages make an app feel polished and trustworthy.

Loading State Pattern

async function sendMessage() {
  const btn = document.getElementById("send-btn");
  const input = document.getElementById("user-input");
  
  // Disable UI during request
  btn.disabled = true;
  btn.textContent = "Thinking...";
  input.disabled = true;
  
  // Show typing indicator
  const typing = createTypingIndicator();
  messagesEl.appendChild(typing);
  
  try {
    const response = await getAIResponse();
    typing.remove();
    displayMessage("assistant", response);
  } catch (error) {
    typing.remove();
    displayError(error.message);
  } finally {
    // Always re-enable UI
    btn.disabled = false;
    btn.textContent = "Send";
    input.disabled = false;
    input.focus();
  }
}

Copy to Clipboard Button

function addCopyButton(messageDiv, text) {
  const copyBtn = document.createElement("button");
  copyBtn.className = "copy-btn";
  copyBtn.textContent = "Copy";
  copyBtn.onclick = async () => {
    await navigator.clipboard.writeText(text);
    copyBtn.textContent = "Copied!";
    setTimeout(() => copyBtn.textContent = "Copy", 2000);
  };
  messageDiv.appendChild(copyBtn);
}
// Glossary Loading state — Visual feedback indicating an async operation is in progress
Typing indicator — An animated element showing the AI is generating a response
finally block — Runs after try/catch regardless of whether an error occurred
Clipboard API — Browser API for reading and writing to the clipboard
📋
// Topic Test · Lesson 9
Lesson 9 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What is the purpose of a loading state?
Makes the app look busy
Provides visual feedback that an operation is in progress preventing users from thinking the app has frozen
Required by browsers
Improves AI response quality
Q2What does the finally block guarantee?
The request succeeds
Code in finally always runs after try/catch regardless of whether an error occurred — perfect for cleanup
The UI stays disabled
An error is thrown
Q3Why disable the input and button during an API call?
API requirement
Prevents duplicate requests being sent while one is already in progress
Saves API tokens
Makes the app look professional
Q4What is a typing indicator?
A keyboard shortcut
An animated element (like three bouncing dots) showing the AI is generating a response
A text cursor
A character counter
Q5What does navigator.clipboard.writeText() do?
Reads text from clipboard
Copies text to the user's system clipboard using the browser Clipboard API
Validates text
Formats text
Q6Why temporarily change Copy to Copied! for 2 seconds?
It is a browser requirement
Provides immediate visual confirmation to the user that the copy action succeeded
The API requires it
It looks professional
Q7What is input.focus() used for after the response?
Clears the input field
Scrolls to the input
Moves keyboard focus back to the input field so the user can immediately type their next message
Validates input
Q8Why remove the typing indicator before displaying the response?
The API removes it
Typing indicators are replaced by the actual response — showing both simultaneously confuses users
It frees memory
The animation stops automatically
Q9What makes an error message user-friendly?
Showing the full JavaScript stack trace
Using technical HTTP codes only
Plain language explaining what went wrong and if possible what the user should do next
Any message is fine
Q10What CSS property creates the animated typing indicator dots effect?
animation with @keyframes defining the bounce movement
JavaScript only
The dots are static images
CSS transitions on opacity
0/10 answered
10
Deploying to GitHub Pages
Repository setup, folder structure, deploy checklist, going live
🔒

Your App is Ready to Ship

GitHub Pages deploys your HTML/CSS/JS app for free with zero configuration. Anyone in the world can access it at a public URL. This lesson walks through the complete deployment process.

Repository Structure

my-ai-app/
├── index.html          (main chat interface)
├── analyser.html       (text analyser)
├── study-buddy.html    (quiz generator)
├── style.css           (shared styles)
├── app.js              (shared JavaScript)
└── README.md           (project documentation)

GitHub Pages Setup Steps

  1. Create a new repository on github.com
  2. Upload all your files (drag and drop in the browser or use git push)
  3. Go to Settings → Pages
  4. Set Source to "Deploy from a branch"
  5. Select branch: main, folder: / (root)
  6. Click Save — your site deploys in 1-2 minutes
  7. Access at: https://yourusername.github.io/repo-name

API Key Security Warning

⚠️ Your API key will be visible in the browser developer tools of anyone who visits your site. For a course project this is acceptable. For a production app serving real users, you must hide the key behind a server (Cloudflare Worker, backend API) so it is never exposed in browser JavaScript.

Pre-Deployment Checklist

  • ✓ Test all three apps locally before pushing
  • ✓ Check API key is set in your JavaScript (not pushed to public repo as a hardcoded secret)
  • ✓ Verify all HTML files link correctly to CSS and JS
  • ✓ Test on mobile viewport
  • ✓ Add a README.md describing the app
// Glossary GitHub Pages — Free static site hosting from a GitHub repository
Deploy — Making your application available on a live server for public access
Cloudflare Worker — A serverless function that can proxy API requests hiding your key
Production app — An app deployed for real users vs a development/learning project
📋
// Topic Test · Lesson 10
Lesson 10 Assessment
10 Questions
⚡ Pass: 60%
0s
Q1What URL format does GitHub Pages use?
yourapp.github.com
github.io/username/repo
yourusername.github.io/repo-name
pages.github.io/yourapp
Q2Where do you enable GitHub Pages in a repository?
Code tab
Issues tab
Settings → Pages
Actions tab
Q3What branch and folder are typically set for GitHub Pages?
gh-pages and /docs
main and /(root)
master and /public
develop and /build
Q4How long does GitHub Pages take to deploy after setup?
Instantly
1-2 minutes typically
10-30 minutes
Up to 24 hours
Q5Why is hardcoding your API key in client-side JavaScript dangerous?
It slows down the app
Anyone visiting your site can view the API key in browser dev tools and use your API credits
GitHub blocks it
The API rejects hardcoded keys
Q6What is a Cloudflare Worker used for in this context?
Hosting the frontend
A serverless function that proxies API requests hiding the API key from browser JavaScript
Managing the database
Optimising images
Q7What file describes your project to other GitHub users?
index.html
config.json
README.md
package.json
Q8What is the difference between a course project and a production app?
Nothing — they are the same
A production app serves real users and requires proper security especially API key protection
Production apps need a backend
Course projects never work in production
Q9What should you test before pushing to GitHub?
Nothing — GitHub catches errors
All app functionality locally across all three apps on both desktop and mobile
Only the main page
Only on desktop
Q10What happens if your HTML file links to a CSS file that does not exist?
Nothing — optional files are ignored
An error appears in the browser console and styles from that file do not load
GitHub Pages auto-creates the file
The site does not deploy
0/10 answered

🏆 AI App Builder Certificate

Complete all 10 lessons with 60%+ to earn your SWAL Learn certificate.

Get Certificate →