DEV Community

Tim Moreton
Tim Moreton

Posted on

Build Your Own AI Chat App in 30 min

Every developer's first website was Hello World. Some HTML, maybe a CSS file, deploy it somewhere, and you get that rush of seeing your thing live on the internet.

With AI, the new Hello World is a chat app.

You still start with a basic index.html. But instead of static text, you wire it up to a backend AI and suddenly you've got something that actually talks back. That's what we're building today.

The stack: Strands SDK for the agent, AgentCore to host it on AWS, Bedrock for model selection, and a simple HTML frontend that hits a proxy to talk to the AI. One TypeScript file, a few CLI commands, and an index.html.

By the end, you'll have a working chat interface backed by a model on Amazon Bedrock (we'll use the ultra-cheap Amazon Nova Micro), deployed to a hosted endpoint, costing fractions of a cent per conversation. And it's the foundation for everything that comes next: memory, authentication, document Q&A, guardrails. But today we keep it dead simple.


The mental model: an agent is a model + a prompt

Before we touch code, here's what we're building:

Browser (HTML + JS) → AgentCore Runtime (hosted endpoint) → Strands Agent → Bedrock (Nova Micro)
Enter fullscreen mode Exit fullscreen mode

You write a Strands agent in TypeScript. The AgentCore CLI deploys it as a hosted endpoint. Your frontend sends messages to that endpoint.

No Lambda functions. No API Gateway. No Docker. Just TypeScript and a couple CLI commands.


Step 1: Set up your environment

You'll need an AWS account, Node.js 22+, and npm.

The fast path: If you use a coding agent (Claude Code, Cursor, Kiro, Codex), the Agent Toolkit for AWS handles credentials, CLI tools, and all of the below for you. Just ask your agent to set up AWS access and it'll run through the steps.

# Your coding agent will run something like:
aws configure agent-toolkit
Enter fullscreen mode Exit fullscreen mode

Or, do it manually:

Configure your AWS credentials:

# Install the AWS CLI (if you don't have it)
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install

# Configure your credentials
aws configure
# (enter your Access Key ID, Secret Access Key, region, output format)

# Verify it works
aws sts get-caller-identity
Enter fullscreen mode Exit fullscreen mode

Once your credentials are set, install the AgentCore CLI and the AWS CDK (AgentCore uses CDK under the hood to deploy your infrastructure):

# The AgentCore CLI
npm install -g @aws/agentcore

# AWS CDK (needed for deployment)
npm install -g aws-cdk
Enter fullscreen mode Exit fullscreen mode

That's your toolchain.


Step 2: Scaffold the project

One command scaffolds the entire project:

agentcore create --name MyAgent --no-agent
cd MyAgent
agentcore add agent \
  --name MyAgent \
  --type create \
  --build CodeZip \
  --language TypeScript \
  --framework Strands \
  --model-provider Bedrock \
  --memory none
Enter fullscreen mode Exit fullscreen mode

What each flag does:

  • --name – The project/agent name (alphanumeric, starts with a letter, max 36 characters).
  • --build – The deployment artifact type. CodeZip means direct code deployment (no Docker).
  • --framework – The agent framework. Supported values: Strands, LangChain_LangGraph, GoogleADK, OpenAIAgents.
  • --language – The language for generated code. Supported values: TypeScript, Python.
  • --model-provider – The model provider. Supported values: Bedrock, Anthropic, OpenAI, Gemini.
  • --memory – Memory configuration. Supported values: none, shortTerm, longAndShortTerm.

Or skip the flags entirely and run agentcore create with no arguments. It launches an interactive wizard that walks you through each option.

You get this structure:

MyAgent/
├── agentcore/
│   ├── agentcore.json        # Project config
│   └── aws-targets.json      # Account + region
└── app/
    └── MyAgent/
        ├── main.ts           # Your agent ← this is the file that matters
        ├── model/load.ts     # Which Bedrock model to use
        ├── package.json      # Dependencies
        └── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Step 3: Write the agent

The scaffold gives you a working agent out of the box.

// app/MyAgent/main.ts

import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';
import { Agent } from '@strands-agents/sdk';
import { loadModel } from './model/load.js';

const agent = new Agent({
  model: await loadModel(),
  systemPrompt: 'You are a helpful AI assistant. Be concise and direct.',
});

const app = new BedrockAgentCoreApp({
  invocationHandler: {
    async *process(payload: any) {
      for await (const event of agent.stream(payload.prompt ?? '')) {
        if (event.event?.delta?.type === 'textDelta') {
          yield { data: event.event.delta.text };
        }
      }
    },
  },
});

app.run({ port: parseInt(process.env.PORT ?? '8080') });
Enter fullscreen mode Exit fullscreen mode

That's the whole thing. ~20 lines.

Three pieces:

  1. Create an agent with a model and a system prompt.
  2. Wire it to the server using BedrockAgentCoreApp, which handles the HTTP endpoints the runtime expects.
  3. Stream tokens back by iterating over agent.stream() and yielding each text delta.

loadModel() points at Amazon Nova Micro by default (Bedrock's cheapest text model, fractions of a cent per conversation). Swap the model ID in model/load.ts if you want Claude Sonnet or something heavier. The full version in the repo adds per-session caching so each conversation keeps its own history, but this is all you need to get a response streaming.


Step 4: Test locally

Before deploying anywhere, run it on your machine:

agentcore dev
Enter fullscreen mode Exit fullscreen mode

In a separate terminal:

agentcore dev "What is the capital of France?"
Enter fullscreen mode Exit fullscreen mode

This installs dependencies, compiles TypeScript, and starts a local server on port 8080. It also opens a browser-based inspector where you can chat with your agent. You should see the response stream back in the second terminal. If that works, your agent runs.

First run takes a moment while it pulls packages and compiles. After that it's near-instant with hot reload.


Step 5: Deploy to AWS

One command:

agentcore deploy
Enter fullscreen mode Exit fullscreen mode

The CLI:

  1. Compiles TypeScript and packages it as a CodeZip archive
  2. Uses CDK to synthesize and deploy CloudFormation (Infrastructure as Code) resources
  3. Creates IAM roles and an AgentCore Runtime endpoint
  4. Configures CloudWatch logging

First deploy takes a few minutes while CDK bootstraps your account. Subsequent deploys are faster. You can run agentcore deploy --dry-run to preview changes without deploying.

Test your deployed agent:

agentcore invoke "Hello! What can you help me with?"
Enter fullscreen mode Exit fullscreen mode

Stream the response in real time:

agentcore invoke "Tell me a joke" --stream
Enter fullscreen mode Exit fullscreen mode

If you see a response, your agent is live on AWS.


Step 6: The frontend

One HTML file. No build step. No npm install.

During local development, this frontend talks to your dev server (agentcore dev on port 8080). For production, the repo includes a Lambda proxy that handles SigV4 signing so browsers can reach the deployed agent. Step 7 covers that.

The core of the frontend is one function:

// Call your agent and stream the response
async function send(text, onToken) {
  const res = await fetch("http://localhost:8080/invocations", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Accept": "text/event-stream", // required: the agent streams SSE
      "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": sessionId, // keeps chat history
    },
    body: JSON.stringify({ prompt: text }),
  });

  // The response is a Server-Sent Events stream: `data: "token"` frames.
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const events = buffer.split("\n\n");
    buffer = events.pop(); // keep any partial frame for the next read
    for (const evt of events) {
      const line = evt.split("\n").find((l) => l.startsWith("data:"));
      if (line) onToken(JSON.parse(line.slice(5).trim()));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Two things bit me here, so they're worth calling out. The server requires the Accept: text/event-stream header. Without it, you get a JSON error object instead of a stream. And the response isn't a single JSON blob. It's an SSE stream of JSON-encoded token strings. In exchange, you get ChatGPT-style token-by-token rendering for free.

The rest is just HTML and CSS to make it look like a chat window. The full index.html (dark theme, chat bubbles, enter-to-send) is in the repo. Clone it, run agentcore dev, open the file in your browser. That's your ChatGPT.

# Grab the frontend
git clone https://github.com/tmoreton/tutorials
open tutorials/index.html
Enter fullscreen mode Exit fullscreen mode

Step 7: Deploy the frontend with GitHub Pages

Push your repo to GitHub:

git init
git add .
git commit -m "initial commit"
git remote add origin git@github.com:YOUR_USERNAME/my-ai-chat.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Then go to your repo on GitHub, click Settings > Pages, set the source to "Deploy from a branch", pick main and / (root), and hit Save:

Github Pages

Give it a minute and your app is live at https://YOUR_USERNAME.github.io/my-ai-chat. HTTPS, free hosting, auto-deploys on every push.

The repo includes a small streaming Lambda proxy behind CloudFront (proxy/) that signs requests to the AgentCore Runtime on behalf of the browser. The hosted frontend talks to this proxy instead of localhost. Deploy it with cd proxy && cdk deploy and update the ENDPOINT constant in index.html to your CloudFront URL.

The CloudFront OAC setup has a couple wrinkles: the x-amz-content-sha256 header and the double invoke permission. The proxy README walks through both. That cost me an afternoon so you don't repeat it.


A few things to keep in mind

The response is a stream, not JSON. The agent server speaks Server-Sent Events and requires the Accept: text/event-stream header. If your frontend tries res.json() expecting a neat {result: "..."} object, you'll get an error back instead. You need to read the stream (see Step 6).

The deployed endpoint is only reachable through the proxy. AgentCore requires SigV4-signed requests, which browsers can't do natively. That's what the Lambda proxy in proxy/ handles for you. Don't try to sign from client-side JS. You'd be shipping AWS credentials in page source.

Limit concurrent calls. This endpoint is unauthenticated, which means anyone with the URL can hit it. Set a concurrency limit on the Lambda proxy or add throttling in CloudFront so you don't wake up to a surprise bill.


The full picture

Layer What How
Model Amazon Nova Micro (swappable) Amazon Bedrock
Agent ~40 lines of TypeScript Strands Agents SDK + AgentCore app server
Backend hosting agentcore deploy AgentCore Runtime
Frontend hosting Push to GitHub GitHub Pages + CloudFront proxy

Right now conversation history lives in memory, so it disappears if the process restarts. Next post, we'll add AgentCore Memory for durable persistence across sessions and restarts.


Clean up

If you want to tear down the AWS resources when you're done:

agentcore remove all
agentcore deploy
Enter fullscreen mode Exit fullscreen mode

The first command resets your config. The second deploys the empty state, which tears down the CloudFormation stack.


Source

The complete code for this tutorial is on GitHub →


If this was useful, follow along. The next post drops next week.

Top comments (0)