This is a TypeScript-based React application built with Vite that serves as the user interface for a full-stack movie and review management system. The app allows users to browse, filter, and explore movies and reviews, with a developer-focused feature: real-time SQL query visibility.
The purpose of the full stack app is to showcase how a Frontend, Backend, and Database work together to deliver the data and interactivty to the end user. The frontend showcases the interaction with the database, upon interactions with the frontend, via dynamic SQL queries displayed directly in the webpage.
This way a user can see eaxctly how each of their actions interacts with a relational database.
React 18 + TypeScript
Vite (ultra-fast dev/build tool)
Tailwind CSS (utility-first styling)
Custom React Hooks for clean state logic
Service Layer for API separation
GitHub Pages for deployment (custom domain: kenwillcode.com)
Movie Explorer: Browse all movies with title, description, genre, rating, theater status
View average review stars calculated from submitted ratings
Movies: Dynamically select filters by rating, genre, average stars, and in-theaters status
Reviews: Dynamically select filters by reviewer type (audience/critic), movie, rating, and stars
See the actual SQL query sent to the database for each dynamic filter/search
Showcases what request are sent to the database (via the Backend) when interacting with the Frontend
Parameters used in each query are displayed below the SQL block
useFetchMovies and useFetchReviews hooks abstract fetch logic
movieService.ts and reviewService.ts encapsulate API interaction
Components are cleanly split (e.g. MovieList, ReviewList, FilterPanel)
Front-End: GitHub Pages kenwillcode.com/movie-db-frontend
Back-End: Node.js + PostgreSQL hosted on Render
API URL managed via VITE_API_URL environment variable
- MovieList.tsx (componenet) ➤ Uses useFetchMovies() custom hook ➤ Renders
- FilterPanel.tsx (component) ➤ Calls onFilter(filters) when the form is submitted
- useFetchMovies.ts (custom hook) ➤ Exposes fetchMovies() which uses movieService.getMovies(filters)
- movieService.ts (service) ➤ Builds a URL with the filters ➤ Calls the API service, which uses fetch(...)
Inside FilterPanel.tsx:
onFilter({ rating: rating || undefined, genre: genre || undefined, inTheaters: inTheaters || undefined, stars: stars || undefined, }); This calls the onFilter function passed from MovieList, with all selected filters.
In MovieList.tsx:
const { movies, sql, params, fetchMovies } = useFetchMovies(); So onFilter(...) becomes fetchMovies(filters).
In useFetchMovies.ts:
const fetchMovies = (filters = {}) => { movieService.getMovies(filters).then((data) => { setMovies(data.data || []); setSql(data.sql || ''); setParams(data.params || []); }); };
In movieService.ts:
ts
CopyEdit
export const getMovies = async (filters: Record<string, any> = {}) => {
const url = new URL(${import.meta.env.VITE_API_URL}/movies
);
Object.entries(filters).forEach(([key, val]) => {
if (val !== undefined && val !== '') {
url.searchParams.append(key, val.toString());
}
});
const res = await fetch(url.toString()); return res.json(); };
The user chooses: • Rating = PG-13 • In Theaters = ✅ • Genre = Horror • Stars = 4 Then the final API call becomes: GET https://your-backend.com/api/movies?rating=PG-13&inTheaters=true&genre=Horror&stars=4
*The same flow is executed for Reviews to deliver the same result.