Objective: Build the web interface for the Student Data Management application using HTML, Tailwind CSS, and JavaScript (with jQuery) to:
- Allow users to input and save student information (student ID, full name, class, birthdate, email) via the POST /students endpoint.
- Display the list of students from the GET /students endpoint.
- Trigger data backup via the POST /backup endpoint.
The interface will be deployed on Amazon S3 (static bucket, section 2.4) and distributed through CloudFront (section 6), integrated with thestudent
API (stageprod
, section 4.8) using the Invoke URL andStudentApiKey
(section 4.2) with CORS security (section 4.7).
API_ENDPOINT
with the Invoke URL (e.g., https://abc123.execute-api.us-east-1.amazonaws.com/prod
) from section 4.8.API_KEY
with the value of StudentApiKey
from section 4.2, securely stored in AWS Secrets Manager or as an environment variable.API_KEY
in AWS Secrets Manager.You need to complete the steps in section 2.4 (create the static S3 bucket), section 4.1 (create the student
API), section 4.2 (create the StudentApiKey
API Key), section 4.3 (create the StudentUsagePlan
Usage Plan), section 4.4 (create the GET /students method), section 4.5 (create the POST /students method), section 4.6 (create the /backup
resource and POST /backup method), section 4.7 (enable CORS), section 4.8 (deploy the API to the prod
stage), section 4.9 (link StudentApiKey
to StudentUsagePlan
and associate with the student
API on stage prod
), and section 3 (create the Lambda functions getStudentData
, insertStudentData
, BackupDynamoDBAndSendEmail
, studentData
DynamoDB table, student-backup-20250706
S3 bucket, SES email verification). Ensure your AWS account is ready and the AWS region is us-east-1
. You also need access to S3, CloudFront, and API Gateway.
index.html
File
index.html
file defines the interface with an input form, function buttons, and a table displaying students.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Student Data Management</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body class="min-h-screen flex items-center justify-center p-6">
<div class="form-container card">
<h1 class="text-4xl font-bold text-center mb-8">Student Management</h1>
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="studentid" class="block text-sm mb-1">Student ID</label>
<input type="text" name="studentid" id="studentid" class="w-full" placeholder="Enter student ID">
</div>
<div>
<label for="name" class="block text-sm mb-1">Full Name</label>
<input type="text" name="name" id="name" class="w-full" placeholder="Enter full name">
</div>
<div>
<label for="class" class="block text-sm mb-1">Class</label>
<input type="text" name="class" id="class" class="w-full" placeholder="Enter class">
</div>
<div>
<label for="birthdate" class="block text-sm mb-1">Birthdate</label>
<input type="date" name="birthdate" id="birthdate" class="w-full">
</div>
<div class="md:col-span-2">
<label for="email" class="block text-sm mb-1">Email</label>
<input type="text" name="email" id="email" class="w-full" placeholder="Enter email">
</div>
</div>
<button id="savestudent" class="btn-primary w-full">Save Student Data</button>
<p id="studentSaved" class="text-center text-green-600 font-medium text-lg"></p>
<button id="getstudents" class="btn-primary btn-secondary w-full">View All Students</button>
<button id="backupstudents" class="btn-primary w-full bg-green-600 text-white p-3 rounded-xl font-semibold hover:bg-green-700">Backup Data</button>
</div>
<div id="showStudents" class="mt-8 overflow-x-auto">
<table id="studentTable" class="w-full">
<thead>
<tr>
<th class="text-sm font-semibold">Student ID</th>
<th class="text-sm font-semibold">Full Name</th>
<th class="text-sm font-semibold">Class</th>
<th class="text-sm font-semibold">Birthdate</th>
<th class="text-sm font-semibold">Email</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>
<script src="scripts.js"></script>
</body>
</html>
styles.css
File
styles.css
file customizes the interface with Tailwind CSS, the Poppins font, and animations.@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
body {
background: linear-gradient(135deg, #4c1d95 0%, #ec4899 100%);
font-family: 'Poppins', sans-serif;
color: #1f2937;
}
.form-container {
background: rgba(255, 255, 255, 0.97);
backdrop-filter: blur(12px);
border-radius: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
padding: 2.5rem;
max-width: 48rem;
margin: 2rem auto;
}
h1 {
background: linear-gradient(to right, #7c3aed, #db2777);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
letter-spacing: -0.025em;
}
input {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 0.75rem 1rem;
transition: all 0.3s ease;
}
input:focus {
outline: none;
border-color: #7c3aed;
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
}
.btn-primary {
background: linear-gradient(90deg, #7c3aed, #db2777);
border-radius: 0.75rem;
padding: 0.75rem;
font-weight: 600;
color: white;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
background: linear-gradient(90deg, #6d28d9, #be185d);
}
.btn-secondary {
background: linear-gradient(90deg, #6b7280, #4b5563);
}
.btn-secondary:hover {
background: linear-gradient(90deg, #4b5563, #374151);
}
table {
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
thead {
background: linear-gradient(to right, #ede9fe, #fce7f3);
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
tbody tr {
transition: background-color 0.3s ease;
}
tbody tr:hover {
background-color: #f5f3ff;
}
.card {
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
label {
font-weight: 500;
color: #374151;
}
scripts.js
File
scripts.js
file handles the logic for making API calls with jQuery, including data validation, HTML escaping, and handling responses/errors.var API_ENDPOINT = "https://710o05k9b6.execute-api.us-east-1.amazonaws.com/prod";
var API_KEY = "hWKpVcoY6246mLB7DdrYb3nWRsYqnLBp35zIxZcd";
// Function to escape HTML to prevent XSS
function escapeHTML(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Handle saving student data (POST)
document.getElementById("savestudent").onclick = function () {
var inputData = {
studentid: $('#studentid').val(),
name: $('#name').val(),
class: $('#class').val(),
birthdate: $('#birthdate').val(),
email: $('#email').val()
};
// Validate fields and email format
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!inputData.studentid || !inputData.name || !inputData.class || !inputData.birthdate || !inputData.email) {
alert("Please fill in all the fields.");
return;
}
if (!emailPattern.test(inputData.email)) {
alert("Invalid email format.");
return;
}
console.log("POST data:", { body: JSON.stringify(inputData) });
$.ajax({
url: API_ENDPOINT,
type: 'POST',
data: JSON.stringify({ body: JSON.stringify(inputData) }),
contentType: 'application/json',
headers: {
'x-api-key': API_KEY
},
success: function (response) {
console.log("POST response:", response);
let message = "Student data has been saved!";
if (response && response.statusCode === 400) {
try {
const error = JSON.parse(response.body || "{}");
message = `Error: ${escapeHTML(error.message || "Unknown error")}`;
} catch (e) {
console.error("Body parsing error:", e);
message = "Error: Unknown";
}
} else if (response && typeof response.body === 'string') {
try {
const data = JSON.parse(response.body);
if (data && data.name && data.studentid) {
message = `Saved student: ${escapeHTML(data.name)} (${escapeHTML(data.studentid)})`;
} else {
console.warn("Response body doesn't contain name or studentid:", data);
}
} catch (e) {
console.error("Body parsing error:", e);
}
} else {
console.warn("No body or non-string body response:", response);
}
document.getElementById("studentSaved").textContent = message;
},
error: function (xhr) {
let errorMessage = "Unknown";
try {
const error = JSON.parse(xhr.responseText || "{}");
errorMessage = error.message || errorMessage;
} catch (e) {
errorMessage = xhr.responseText || errorMessage;
}
alert("Error while saving: " + errorMessage);
}
});
};
// Handle fetching all students (GET)
document.getElementById("getstudents").onclick = function () {
$.ajax({
url: API_ENDPOINT,
type: 'GET',
contentType: 'application/json',
headers: {
'x-api-key': API_KEY
},
success: function (response) {
console.log("GET response:", response); // Log for debugging
$('#studentTable tbody').empty();
let students = response;
// Check if the response is an object containing body
if (response && typeof response.body === 'string') {
try {
students = JSON.parse(response.body);
} catch (e) {
console.error("Body parsing error:", e);
alert("Returned data is not in JSON format.");
return;
}
}
// Check if students is an array
if (Array.isArray(students)) {
if (students.length === 0) {
alert("No student data.");
} else {
jQuery.each(students, function (i, data) {
$("#studentTable tbody").append(
`<tr>
<td class='p-4'>${escapeHTML(data.studentid)}</td>
<td class='p-4'>${escapeHTML(data.name)}</td>
<td class='p-4'>${escapeHTML(data.class)}</td>
<td class='p-4'>${escapeHTML(data.birthdate)}</td>
<td class='p-4'>${escapeHTML(data.email)}</td>
</tr>`
);
});
}
} else {
console.warn("Returned data is not an array:", students);
alert("Returned data is not in the correct format.");
}
},
error: function (xhr) {
let errorMessage = "Unknown";
try {
const error = JSON.parse(xhr.responseText || "{}");
errorMessage = error.message || errorMessage;
} catch (e) {
errorMessage = xhr.responseText || errorMessage;
}
alert("Error fetching student data: " + errorMessage);
}
});
};
document.getElementById("backupstudents").onclick = function(){
$.ajax({
url: API_ENDPOINT + "/backup",
type: 'POST',
data: JSON.stringify({}),
contentType: 'application/json; charset=utf-8',
headers: {
'x-api-key': API_KEY
},
success: function (response) {
alert("Backup successful! Check your email for the backup file.");
},
error: function () {
alert("Error performing data backup.");
}
});
};
API_ENDPOINT
and API_KEY
with actual values from section 4.8 and 4.2.API_ENDPOINT
and API_KEY
https://abc123.execute-api.us-east-1.amazonaws.com/prod
with the Invoke URL from section 4.8 (retrieve from API Gateway > Stages > prod
> Invoke URL).xxxxxxxxxxxxxxxxxxxx
with the StudentApiKey
value from section 4.2.StudentApiKey
in AWS Secrets Manager and retrieve it in a serverless environment (e.g., Lambda or CloudFront Functions).const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager({ region: 'us-east-1' });
async function getApiKey() {
const data = await secretsManager.getSecretValue({ SecretId: 'student-api-key' }).promise();
return JSON.parse(data.SecretString).apiKey;
}
API_KEY
in environment variables (e.g., .env
with Vite/React) or configure CloudFront to automatically add the x-api-key
header.Factor | Details |
---|---|
API Key Security | Do not hardcode API_KEY directly in scripts.js . Use AWS Secrets Manager or CloudFront Functions to add the x-api-key header automatically. |
CORS | Ensure CORS is correctly enabled (section 4.7) with Access-Control-Allow-Origin: https://d12345678.cloudfront.net . If encountering CORS errors, check headers in Method Response and Lambda (sections 3.1, 3.2, 3.3). |
AWS Region | Ensure the region us-east-1 matches with student API, stage prod , Lambda functions, DynamoDB studentData , S3 student-backup-20250706 , SES, and CloudFront. |
Optimization | - Minimize the styles.css and scripts.js files before uploading them to S3 to speed up loading. - Use CloudFront cache to store static content, set a reasonable TTL (e.g., 86400 seconds). - Add Request Validator in API Gateway (section 4.5) to check the JSON body of POST /students. - Enable CloudWatch Logs for API Gateway (section 4.8): In API Gateway > Stages > prod > Logs/Tracing, select Enable CloudWatch Logs. |
Error Checking | - If the interface doesn’t load: Check S3 public access and CloudFront origin. - If the API returns 403 : Check StudentApiKey and StudentUsagePlan (section 4.9). - If the API returns 429 : Check the limits in StudentUsagePlan (section 4.3). - If the API returns 500 : Check the CloudWatch logs for Lambda. - If the table is empty or data isn’t displaying: Check the response from GET /students in Developer Tools > Network. |
API Integration Testing | - Use Developer Tools to check API requests. - Check DynamoDB studentData to verify new records. - Check S3 student-backup-20250706 to verify the backup file. - Check email (including Spam/Junk) to verify notifications from SES. |
Best Practice Tip: Before deploying to S3, test the interface locally using
npx serve
or a static server to ensure that API requests work correctly. Use Developer Tools > Network to verify thex-api-key
header and the response from the API. Check the data in DynamoDB, S3, and SES after each action.
The web interface has been built with index.html
, styles.css
, and scripts.js
, successfully integrated with the student
API (stage prod
) using Invoke URL and StudentApiKey
. The application supports data entry, displays a list of students, and performs data backup, ready to be deployed on S3 and distributed through CloudFront.
Next step: Proceed to Configuring CloudFront and Deploying the Web Interface to complete the integration!