Context: Personal React + AWS API Gateway prototype (private repo). Built to practice CRUD flows, optimistic UI, and serverless deploys. No production users.
AI assist: ChatGPT/Copilot scaffolded some components and Lambda handlers. Prompt logs live in the repo so it’s clear what code I edited.
Status: Demo runs on Vercel (frontend) + AWS API Gateway/Lambda/DynamoDB (backend). Auth, uploads, and Terraform modules are still TODOs.

Reality snapshot

  • Frontend: React 18 + Vite, controlled forms, modals, context for auth state, optimistic updates. Hosted on Vercel.
  • Backend: AWS API Gateway → Lambda (Node 20) → DynamoDB. API keys + throttling guard the endpoints.
  • External data: Jikan API provides trending anime metadata. Cached per session to avoid hitting rate limits.
  • Limitations: Auth is mocked, backend cold starts add ~2 s sometimes, and the repo is private until I scrub secrets.

Architecture

ReactJSMobileApp/
├── src/
│ ├── App.jsx
│ ├── api.js
│ ├── components/
│ └── hooks/useFetch.js
├── amplify/ (legacy)
├── lambda/
│ ├── createCharacter.js
│ ├── listCharacters.js
│ ├── updateCharacter.js
│ └── deleteCharacter.js
└── vercel.json
  • vercel.json proxies /api/* requests to API Gateway, injecting the API key server-side so the browser never sees it.
  • Lambda functions share a thin validation layer and log request IDs for tracing.

Client flow highlights

Fetch & cache characters

useEffect(() => {
let active = true;
(async () => {
setLoading(true);
try {
const { data } = await api.get("/api/characters");
if (active) setCharacters(data);
} catch (error) {
if (active) setError("Unable to load characters right now.");
} finally {
if (active) setLoading(false);
}
})();
return () => {
active = false;
};
}, []);
  • Guards prevent state updates after unmount. Errors bubble into a toast + log entry.

Optimistic updates

const handleUpdate = async (character) => {
const snapshot = characters;
setCharacters((prev) =>
prev.map((item) => (item.id === character.id ? character : item))
);
try {
await api.put(`/api/characters/${character.id}`, character);
} catch {
setError("Could not save changes. Reverting.");
setCharacters(snapshot);
}
};
  • Keeps the UI responsive even when API Gateway cold starts. Rollback path restores prior state if Lambda fails.

Filtering with memoized selectors

const filteredCharacters = useMemo(() => {
if (selectedCategory === "all") return characters;
return characters.filter((character) => {
const category = character.category?.toLowerCase();
const role = character.role?.toLowerCase();
return category === selectedCategory || role === selectedCategory;
});
}, [characters, selectedCategory]);
  • Prevents unnecessary renders when the dataset grows.

Deployment + security notes

  • Vercel Routes proxy requests to API Gateway, injecting x-api-key. Rate limits + throttles configured in API Gateway.
  • Lambda validates payloads with ajv, normalizes responses, and logs structured JSON.
  • Terraform/SAM scripts exist but aren’t production-ready—still manual deployments via sam deploy.
  • Environment variables live in Vercel secrets + AWS Parameter Store.

Challenges & fixes

  • CORS headaches: Solved by proxying through Vercel and enabling CORS on API Gateway.
  • Slow cold starts: Mitigated with optimistic UI + skeleton loaders. Future improvement = provisioned concurrency or scheduled warmers.
  • Schema drift: Added JSON schema validation + Jest tests so the frontend can’t send malformed payloads.
  • Rate limiting: Respect Retry-After from Jikan; UI shows a friendly message and caches the last successful response.

TODO list

  • Add Cognito auth + user-specific lists.
  • Upload images to S3 instead of relying on remote URLs.
  • Finish Terraform modules (VPC, API Gateway, DynamoDB) and publish them.
  • Write Playwright tests covering the top flows.
  • Sanitize the repo so I can make it public without leaking secrets.

Links