- Create a Lambda function that processes HTTP requests
- Expose the Lambda function via API Gateway
- Update their S3-hosted website to call the API
- Integrate frontend forms with serverless backend logic
- Go to AWS Lambda → Create function
- Function name:
GreetingFunction - Runtime: Python 3.11 (or latest available)
- Create function
- Replace the code in the editor with:
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info(f'Event received: {event}')
try:
# Parse the incoming request body
if isinstance(event.get('body'), str):
body = json.loads(event['body'])
else:
body = event.get('body', {})
except json.JSONDecodeError:
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST'
},
'body': json.dumps({'error': 'Invalid JSON in request body'})
}
name = body.get('name', '').strip()
# Validate that name was provided
if not name:
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST'
},
'body': json.dumps({'error': 'Name is required'})
}
# Return the greeting
response = {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST'
},
'body': json.dumps({
'message': f'Hello {name}!'
})
}
return responseLambda Function Code Breakdown
- Click Deploy
- Go to Test tab
- Create a new test event with:
{
"body": "{\"name\": \"Alice\"}"
}- Click Test and verify it returns
{"message": "Hello Alice!"}
- Go to API Gateway → Create API
- Choose REST API → Build
- API name:
GreetingAPI - Endpoint type: Regional
- Click Create API
- Select root
/and Create resource - On the right give your resource a name (e.g.
greet)
- In Resources, select your resource (e.g.
greet) - Click Create Method → POST
- Integration type: Lambda Function
- Lambda Function:
GreetingFunction - Enable Lambda Proxy Integration.
- Click Save
- Accept any Lambda permission popup
- In Resources, select your resource (e.g.
greet) - Click Create Method → OPTIONS
- Select Integration type:
Mock - Click Save
- Navigate back to your API and resource, and select OPTIONS
- Add the following
Method Response Headers:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Content-Type- Navigate to Integration Response
- Add the following
header mappings:
Access-Control-Allow-Origin: '*'
Access-Control-Allow-Methods: 'OPTIONS,POST'
Access-Control-Allow-Headers: 'Content-Type'
Content-Type: 'application/json'CORS stands for Cross-Origin Resource Sharing. It's a security feature implemented by web browsers to control how web pages can request resources from a different domain (origin) than the one that served the web page.
By default, browsers block these cross-origin requests unless the server at api.anotherdomain.com explicitly allows it. This prevents malicious websites from making unauthorized requests to other sites on behalf of a user.
If you're using API Gateway + Lambda, you need to configure CORS in API Gateway to allow your frontend (e.g., hosted on a different domain) to communicate with the backend.
- Select the POST method
- Click Method Request
- Go back and click on the root
/ - Click Enable CORS and replace existing CORS headers
- Check all methods and codes (4XX, 5XX, POST, OPTIONS)
- Click Enable CORS
- Click Deploy API
- Deployment stage: Create new stage called
prod - Click Deploy
- Copy your Invoke URL (looks like:
https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/stage/resource)
NOTE: You may need to add the
stageandresourcemanually
Add this to your website's HTML file. Update the API_ENDPOINT variable with your actual API Gateway URL:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Greeting App</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.form-container {
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
background-color: #f9f9f9;
}
input {
padding: 10px;
width: 100%;
max-width: 300px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.response {
margin-top: 20px;
padding: 15px;
border-radius: 4px;
display: none;
}
.response.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block;
}
.response.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block;
}
.loading {
display: none;
margin-top: 10px;
}
.loading.show {
display: block;
}
</style>
</head>
<body>
<h1>Greeting Application</h1>
<div class="form-container">
<h2>Enter Your Name</h2>
<form id="greetingForm">
<input
type="text"
id="nameInput"
placeholder="Enter your name"
required
>
<button type="submit">Submit</button>
</form>
<div class="loading" id="loading">
Loading...
</div>
<div class="response" id="response"></div>
</div>
<script>
// Replace this with your actual API Gateway endpoint
const API_ENDPOINT = 'https://[YOUR_API_ENDPOINT]/[STAGE]/[RESOURCE]';
const form = document.getElementById('greetingForm');
const nameInput = document.getElementById('nameInput');
const responseDiv = document.getElementById('response');
const loadingDiv = document.getElementById('loading');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const name = nameInput.value.trim();
if (!name) {
showResponse('Please enter your name', 'error');
return;
}
// Show loading state
loadingDiv.classList.add('show');
responseDiv.classList.remove('success', 'error');
try {
const response = await fetch(API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: name })
});
const data = await response.json();
if (response.ok) {
showResponse(data.message, 'success');
nameInput.value = ''; // Clear input
} else {
showResponse(data.error || 'An error occurred', 'error');
}
} catch (error) {
console.error('Error:', error);
showResponse('Failed to connect to the server. Check the console.', 'error');
} finally {
loadingDiv.classList.remove('show');
}
});
function showResponse(message, type) {
responseDiv.textContent = message;
responseDiv.className = `response ${type}`;
}
</script>
</body>
</html>- Replace (or add to) your existing HTML file with the code above
- Update the
API_ENDPOINTvariable with your actual API Gateway URL - Commit and push to GitHub
- Your GitHub Actions workflow will automatically deploy to S3
- Visit your S3 website
- Enter a name and click Submit
- You should see "Hello [name]!" appear below the form
No response or blank response:
- Check browser console (F12) for errors
- Verify
API_ENDPOINTURL is correct - Confirm Lambda function is deployed
- Check API Gateway stage is correct (
/prod)
CORS errors:
- Confirm CORS was enabled in API Gateway
- Error message will mention "Access-Control-Allow-Origin"
400 Bad Request:
- Verify the Lambda function is receiving the JSON with
namefield - Check CloudWatch logs in Lambda console
Check Lambda Logs:
- Go to
Lambda→GreetingFunction→Monitor→Logs - View the most recent log stream to see what the function received
After the basic project works, learners can:
- Add more form fields (email, message)
- Store submissions in DynamoDB
- Add authentication via Cognito
- Create different endpoints for different operations
- Add error handling and validation on the backend
- Use environment variables to make the API endpoint configurable