In Part 1, you built MinuteTalk AI with a React frontend, a FastAPI backend that talks to Groq, and a 60-second impromptu speaking timer. It works on your laptop. Now it's time to put it on the internet.
In this part, you'll deploy the whole thing on AWS with a fork-and-deploy workflow:
- The React frontend goes to Amplify Hosting, which builds it from your GitHub repo and serves it over HTTPS.
- The FastAPI backend goes to AWS Lambda, wrapped by the Mangum handler you added in Part 1.
- An HTTP API Gateway sits in front of Lambda and gives you a public URL.
- The infrastructure is defined with the AWS Cloud Development Kit (CDK) in TypeScript, so it's repeatable and version-controlled.
The best part: you don't need Docker, and you don't need to configure IAM credentials on your laptop. Everything happens in your browser, the Amplify console for the frontend, and AWS CloudShell for the backend.
Deployment Architecture
Here's the target state after you finish:
The same FastAPI code that ran on your laptop runs unchanged inside Lambda. The only additions are the Mangum handler and a Lambda-compatible package.
Prerequisites
You need:
- An AWS account with administrative access
- A GitHub account with your fork of the project repository
- Your Groq API key from console.groq.com
No local AWS CLI, no Docker, and no Terraform.
Step 1: Deploy the Frontend with Amplify Hosting
Amplify Hosting is a managed static site host with a built-in continuous integration and continuous delivery (CI/CD) pipeline. It watches your GitHub repo, runs the build, and serves the result.
Fork the repository
On GitHub, open the project repository and click Fork to create your own copy.
Create the Amplify app
- Sign in to the AWS Management Console and open AWS Amplify.
- Click Create new app and select GitHub as the provider.
- Authorize Amplify to access your repositories, then select your fork and the
mainbranch. - Amplify reads
amplify.ymlfrom the repo, which tells it how to build the React app:
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: dist
files:
- '**/*'
- Click Save and deploy.
Amplify now clones your repo, runs npm ci and npm run build, and serves the dist folder. Within a few minutes, you get a URL like https://main.d1234567890abc.amplifyapp.com.
The frontend deploys before the backend exists, so the topic button will fail until Step 4. That's expected and you'll connect them later.
Step 2: Build a Lambda-Compatible Backend Package
This step is where most people get stuck, and it's worth understanding why.
AWS Lambda runs Linux x86_64 with Python 3.11. Your laptop runs something else, Windows, macOS, or a different Python version. Some Python packages ship compiled native binaries, and those binaries are specific to one OS and Python version.
pydantic-core, the Rust-powered engine inside Pydantic, is exactly that kind of package. If you install it with a plain pip install -t ./package, you get a binary for your machine. Lambda then fails to start with:
Unable to import module 'app.main':
No module named 'pydantic_core._pydantic_core'
The fix is to tell pip to download wheels for Lambda's platform, not yours. The build_package.py script in the backend folder does this for you:
pip install -r requirements.txt \
--platform manylinux2014_x86_64 \
--target ./package \
--only-binary=:all: \
--implementation cp \
--python-version 3.11
Reading the flags:
-
--platform manylinux2014_x86_64— the Linux standard Lambda uses -
--python-version 3.11— matches the Lambda runtime -
--only-binary=:all:— never compile from source on your machine -
--target ./package— install into a clean folder
Run the script to produce deployment.zip, which contains the Linux-targeted dependencies plus your app/ source:
cd backend
python build_package.py
You can verify the result contains the correct binary:
python -c "import zipfile; print([n for n in zipfile.ZipFile('deployment.zip').namelist() if 'pydantic_core' in n and n.endswith(('.so','.pyd'))])"
# ['pydantic_core/_pydantic_core.cpython-311-x86_64-linux-gnu.so']
The .so extension means it's a Linux binary for CPython 3.11 and that's exactly what Lambda needs.
Step 3: Deploy the Backend with CDK
You define the backend infrastructure once, in TypeScript, and CDK provisions it for you. Open amplify/backend.ts, it contains the whole story.
The Lambda function
The lambda.Function construct creates the function and points it at your deployment package:
const backendFunction = new lambda.Function(this, 'MinuteTalkBackend', {
runtime: lambda.Runtime.PYTHON_3_11,
handler: 'app.main.handler', // Mangum handler in app/main.py
code: lambda.Code.fromAsset(
path.join(__dirname, '..', '..', 'backend', 'deployment.zip'),
),
timeout: Duration.seconds(30),
memorySize: 512,
environment: {
GROQ_API_KEY: process.env.GROQ_API_KEY || '',
},
});
The API Gateway
An HTTP API (v2) proxies every request to Lambda and enables Cross-Origin Resource Sharing (CORS) so the browser can call it from the Amplify domain:
const api = new apigatewayv2.HttpApi(this, 'MinuteTalkApi', {
apiName: 'MinuteTalk AI API',
corsPreflight: {
allowHeaders: ['*'],
allowMethods: [apigatewayv2.CorsHttpMethod.ANY],
allowOrigins: ['*'],
maxAge: Duration.days(1),
},
});
api.addRoutes({
path: '/{proxy+}',
methods: [apigatewayv2.HttpMethod.ANY],
integration: lambdaIntegration,
});
The /{proxy+} catch-all route means the frontend can call /api/topic, /api/health, or any future endpoint without changing the gateway.
Deploy from CloudShell
AWS CloudShell is a browser-based Linux terminal that comes with your AWS credentials already configured. It's the fastest way to run CDK without setting up anything locally.
- In the AWS console, click the CloudShell icon (
>_) in the top bar. - Clone your fork and install the CDK dependencies:
git clone https://github.com/YOUR_USERNAME/minutetalk-ai.git
cd minutetalk-ai/amplify
npm ci
npm run build
- Export your Groq key and deploy:
export GROQ_API_KEY="your-groq-api-key"
npx cdk bootstrap
npx cdk deploy --require-approval never
cdk bootstrap prepares the account once; cdk deploy creates the Lambda and the API Gateway.
When it finishes, the output includes the ApiEndpoint URL:
MinuteTalkBackendStack.ApiEndpoint = https://a1b2c3d4.execute-api.us-east-1.amazonaws.com
Copy that URL. You'll need it in the next step.
Step 4: Connect the Frontend to the Backend
The frontend reads its API URL from a Vite environment variable at build time. You can't change it at runtime, you set it once and rebuild.
- In the Amplify console, open your app and go to Environment variables under App settings.
- Add a variable:
-
Key:
VITE_API_URL - Value: the API endpoint URL from Step 3, without a trailing slash
-
Key:
- Click Save, then Redeploy the app.
The vite.config.js bakes this value into the JavaScript bundle:
define: {
'process.env.VITE_API_URL': JSON.stringify(
env.VITE_API_URL || (mode === 'development' ? 'http://localhost:8000' : ''),
),
},
In production, if VITE_API_URL is missing, the build bakes an empty string. The app then shows a clear message instead of silently failing, which brings us to verification.
Step 5: Verify the Deployment
Before you open the frontend, confirm the backend is healthy:
curl https://a1b2c3d4.execute-api.us-east-1.amazonaws.com/api/health
You should see:
{"status":"healthy","message":"Groq API connected"}
Then open your Amplify URL, pick Technology, and you should get a topic, a running timer, and hints.
Troubleshooting
Two errors account for almost every deployment problem with this stack.
Error: No module named 'pydantic_core._pydantic_core'
This is the platform mismatch from Step 2. The Lambda package contains a binary built for your machine instead of Linux.
Fix: rebuild the package with python build_package.py (which targets manylinux2014_x86_64 and Python 3.11), then redeploy with cdk deploy.
Error: Frontend shows "Failed to fetch"
"Failed to fetch" is the browser's generic message for a network or CORS failure. Work through this checklist:
-
Is the Lambda healthy? Open
/api/healthin a browser tab. If it errors, fix the backend first, a broken Lambda also breaks the browser's CORS preflight, which surfaces as "Failed to fetch". -
Is
VITE_API_URLset? Check the Amplify environment variables and confirm you redeployed after changing them. -
Is there a trailing slash? The value must end without
/, or the request URL becomes//api/topic. -
Is CORS enabled? Both the API Gateway (
corsPreflight) and FastAPI (CORSMiddleware) returnAccess-Control-Allow-Origin: *. If you restricted origins, make sure your Amplify domain is on the list.
Error: 503 on /api/topic
The backend can't reach Groq. Check that GROQ_API_KEY is set on the Lambda (it's injected from the environment at cdk deploy time) and that the key is valid.
Clean Up
Serverless services are cheap, but they aren't free forever. When you're done:
-
Backend: In the CloudFormation console, select the
MinuteTalkBackendStackstack and click Delete. - Frontend: In the Amplify console, open your app, click Actions, then Delete app.
This removes the Lambda, the API Gateway, and the hosting resources you created.
Recap
You deployed a full-stack AI application on AWS with a browser-only workflow:
- Amplify Hosting built and served the React frontend from GitHub.
- AWS Lambda + Mangum ran your FastAPI backend unchanged.
- API Gateway exposed it over HTTPS with CORS enabled.
- AWS CDK defined all of it in TypeScript, so it's reproducible.
- CloudShell ran the deployment with your credentials already configured.
The two gotchas you'll remember: target Lambda's Linux platform when you build the package, and set VITE_API_URL before you build the frontend.
Ideas for Going Further
The app is a solid demo, but it's also a foundation. Try these next:
-
Add categories — the fallback topics and prompts live in dictionaries in
main.py; extend them and redeploy. - Add authentication — swap the permissive CORS and public API for Cognito or a simple API key.
-
Switch models — the backend already probes available Groq models; you can swap in
llama3-8b-8192or another model with one line. - Add persistence — store practice sessions in DynamoDB so users can track their progress.



Top comments (0)