Skip to content

About

React + TypeScript movie dashboard built with Vite, TanStack Query, TanStack Router, Axios, Redux Toolkit, Tailwind CSS, shadcn/ui, REST, and GraphQL

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Movie UI Dashboard

Vite React TypeScript TanStack Query TanStack Router GraphQL Tailwind CSS Redux Toolkit Vitest

Movie UI Dashboard is a responsive React dashboard for browsing, filtering, sorting, and maintaining movie records. The project was built to demonstrate practical trade-offs between REST and GraphQL data access while keeping the UI experience consistent across both API modes.

The dashboard includes movie search, genre filtering, date and revenue range filters, pagination, sorting, create/edit/delete workflows, theme switching, and an API diagnostics drawer for observing request behavior.

Architecture Summary

Movie UI Dashboard is the frontend layer of a larger movie platform system. It connects to REST and GraphQL APIs backed by a shared PostgreSQL data platform, allowing the same UI to compare different API access patterns without changing the user experience.

The frontend intentionally abstracts the transport layer so UI components, hooks, and feature workflows remain independent from whether data is delivered through REST or GraphQL.

Why This Project Exists

This project was designed to explore how modern frontend applications can remain transport-agnostic while supporting multiple backend access patterns.

The goal was to demonstrate:

  • reusable frontend architecture
  • scalable server-state management
  • REST vs GraphQL trade-offs
  • predictable data-fetching patterns
  • production-minded React architecture

β€œThe UI experience stays the same β€” the transport layer changes.”

🧩 Core Design Principles

The dashboard is built around several key frontend engineering principles:

  • transport-agnostic UI architecture
  • reusable feature-based organization
  • scalable server-state management
  • predictable query behavior
  • separation of UI state and remote state
  • reusable filtering workflows
  • consistent API integration patterns

The goal is to keep the frontend flexible, maintainable, and independent from backend implementation details.

πŸ’‘ Architecture Insight

The dashboard intentionally separates UI workflows from transport implementation details. REST and GraphQL requests ultimately produce the same frontend data shape, allowing components, hooks, tables, dialogs, and filtering systems to remain reusable across both API modes.

πŸ—οΈ System Architecture

The dashboard is designed around a transport-agnostic frontend architecture that can communicate with both REST and GraphQL APIs backed by a shared PostgreSQL platform.

Movie UI Dashboard Architecture

πŸ”„ Request Lifecycle

sequenceDiagram
    participant UI as React Dashboard
    participant Query as TanStack Query
    participant API as REST / GraphQL API
    participant DB as PostgreSQL Platform

    UI->>Query: Trigger movie query

    alt REST Mode
        Query->>API: Axios REST request
    else GraphQL Mode
        Query->>API: GraphQL request
    end

    API->>DB: Execute shared query logic

    DB-->>API: Filtered movie data

    API-->>Query: Response payload

    Query-->>UI: Cached server state
Loading

πŸŽ₯ Architecture Walkthrough

Movie UI Dashboard Walkthrough


🎬 Demo

✏️ Update Movie GraphQL

Update Movies GraphQL

✏️ Update Movie REST

Update Movies REST

πŸ” Filter Movies

Filter Movies

✨ Create Movie

Create Movies

πŸ” Search Movies

Search Movies

⏩ Pagination

Pagination

πŸ” Sort Movies

Sort Movies

🎨 Delete & Theme

Theme

Tech Stack

Area Technologies
Frontend React 19, TypeScript, Vite
Routing TanStack Router with file-based route generation
Server state TanStack Query
API clients Axios for REST, graphql-request for GraphQL
UI state Redux Toolkit, React hooks
Styling Tailwind CSS 4, shadcn/ui, Radix UI, Geist font
Theme next-themes light/dark mode
Forms and typed validation React Hook Form resolvers, typed request payloads
Feedback Sonner toast notifications
Icons Lucide React
Testing Vitest, Testing Library, jsdom
Tooling ESLint, Prettier, TypeScript project references

Features

  • Toggle between REST and GraphQL data sources from the same movie listing screen.
  • Search movies by multiple search modes.
  • Filter by genre, release date range, worldwide gross, domestic gross, and production budget.
  • Sort and paginate server-backed movie results.
  • Keep previous results visible while new filter requests are loading.
  • Select GraphQL fields to control which columns and payload fields are requested.
  • Create, update, and delete movie records.
  • Show API request diagnostics for loading, placeholder, and refresh states.
  • Support responsive layouts with light and dark themes.

Project Structure

src/
  components/              Reusable app components and shadcn/ui primitives
  config/                  Runtime API target helpers
  features/
    apiMode/               Redux slice for REST/GraphQL mode
    genres/                Genre API, types, and query hook
    graphql/               Redux slice for selected GraphQL columns
    movies/                Movie APIs, hooks, components, filters, types, tests
  hooks/                   Shared hooks
  layouts/                 App shell layout
  lib/                     API client, utilities, request diagnostics helpers
  pages/                   Page-level React components
  routes/                  TanStack Router file routes
  store/                   Redux store and selectors
  test/                    Vitest setup
  main.tsx                 Application entry point
  router.tsx               TanStack Router instance

Modern frontend applications need predictable server-state management to handle caching, background refetching, pagination, filtering, and mutation synchronization without duplicating networking logic across components.

TanStack Query

TanStack Query is used as the server-state layer for both REST and GraphQL. The root app creates a QueryClient in src/main.tsx and provides it through QueryClientProvider, so feature hooks can read cached data, trigger background refetches, and invalidate queries after mutations.

The main movie hook is useMovies in src/features/movies/hooks/useMovies.ts. It builds one query abstraction over two transport layers:

useQuery({
  queryKey: ['movies', apiMode, filters, selectedFields],
  queryFn: () =>
    apiMode === 'graphql'
      ? getGraphQlMovies(filters, selectedFields)
      : getMovies(filters),
  placeholderData: keepPreviousData,
})

This query key intentionally includes:

  • apiMode, so REST and GraphQL results are cached separately.
  • filters, so search, sort, pagination, date, genre, and money range changes produce the correct server request.
  • selectedFields, so GraphQL field selection changes create a new query result instead of reusing a payload with missing fields.

keepPreviousData keeps the table populated while new pages or filters are loading. That creates a smoother dashboard experience because the UI does not flash empty content between query changes.

Genres are loaded with useGenres in src/features/genres/hooks/useGenres.ts. Genre data is treated as long-lived reference data with staleTime: Infinity and gcTime: Infinity, which prevents unnecessary refetching for a mostly static list.

Mutations live in src/features/movies/hooks/useUpsertMovie.ts. Create, update, and delete actions use useMutation, then invalidate the shared ['movies'] query family on success:

await queryClient.invalidateQueries({ queryKey: ['movies'] })

That keeps the listing synchronized after a movie record changes, regardless of the current page, filter, or API mode.

REST Implementation

REST requests are implemented with Axios in src/features/movies/api/movies-api.ts and the shared Axios instance in src/lib/api.ts.

The shared client:

  • Reads the base URL from VITE_API_URL.
  • Sends JSON requests by default.
  • Adds request metadata for API diagnostics.
  • Tracks request start and finish timing.
  • Shows normalized error messages through Sonner toasts.

Movie list filters are converted into query string parameters in src/features/movies/api/movie-query-params.ts. This keeps URL parameter construction separate from the React hook and makes the REST transport easier to test.

REST endpoints used by the movie feature:

GET    /movies
POST   /movies
PUT    /movies/:id
DELETE /movies/:id

The REST path supports the full create, read, update, and delete workflow.

GraphQL Implementation

GraphQL requests are implemented in src/features/movies/api/movie-graphql-api.ts with graphql-request.

The GraphQL list query is built dynamically from the selected movie fields. The app always requests required identity/display fields such as id and movieName, then adds whichever optional fields are currently selected in the UI:

const columns = `
  id movieName ${requestedFields.join(' ')}
`

That selected field list is also part of the TanStack Query key, so changing visible GraphQL columns triggers a fresh request with the matching payload shape.

GraphQL filters are passed as variables instead of interpolated strings:

request(getGraphQlUrl(), query, {
  filters: params,
})

This keeps filter values separate from the query document and allows the API to receive a typed MovieFiltersInput.

GraphQL results are mapped back into the same MovieResponse shape used by REST. This is important because the table and dialog components can stay transport-agnostic:

items: data.movies.items.map(mapGraphqlMovie)

GraphQL currently supports listing, updating, and deleting movies. Creating movies is intentionally guarded with an error message in the GraphQL API module because creation is only available in REST mode.

TanStack Router

TanStack Router is configured in src/router.tsx:

export const router = createRouter({ routeTree })

The app uses file-based routes from the src/routes directory. The TanStack Router Vite plugin in vite.config.ts generates src/routeTree.gen.ts, which is then passed into createRouter.

Current route files:

  • src/routes/__root.tsx defines the root route and wraps every page with AppLayout.
  • src/routes/index.ts defines the / route and renders MoviesPage.

The router type is registered globally with TanStack Router's module augmentation:

declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}

That gives route links, navigation, params, and router APIs strong TypeScript awareness across the app.

State Management

The project separates server state from UI state:

  • TanStack Query owns remote data, caching, loading states, and mutation invalidation.
  • Redux Toolkit stores cross-component UI preferences such as the selected API mode and selected GraphQL fields.
  • Local React state manages page-specific filter controls before they become query filters.

Redux slices:

  • src/features/apiMode/apiModeSlice.ts stores whether the dashboard is using REST or GraphQL.
  • src/features/graphql/graphqlColumnsSlice.ts stores selected GraphQL field keys and normalizes invalid values.

The movie page combines those slices with local filter state in src/pages/MoviesPage.tsx.

Filtering and Debouncing

Movie filters are managed by useMovieFilterState in src/features/movies/hooks/useMovieFilterState.ts.

Search input, genres, date ranges, and money ranges are debounced before they are passed to TanStack Query. This prevents the app from making a server request for every keystroke or every intermediate range adjustment.

The debounced values are converted into a clean filter patch with buildDebouncedFilterPatch, then combined with pagination and sorting to form the final movieFilters object used by useMovies.

Styling and UI

The UI is built with Tailwind CSS 4 and shadcn/ui-style primitives.

Key styling pieces:

  • src/index.css imports Tailwind, shadcn styles, animation utilities, and the Geist variable font.
  • CSS custom properties define light and dark design tokens.
  • next-themes toggles the dark class on the document.
  • Reusable UI primitives live in src/components/ui.
  • Feature-specific controls live beside the movie feature in src/features/movies/components.

The layout is intentionally table-first and optimized for dashboard-style workflows. Filters, pagination, diagnostics, and mutation dialogs are designed to support repeated data-management workflows instead of a marketing-style page.

Backend Requirements

This dashboard requires one of the Movie API backends to be running:

  • DotNetMovieApi
  • NodeMovieApi

Both APIs connect to the shared Postgres Movie Platform and expose movie data through REST and GraphQL.

Default API URL:

VITE_API_URL=http://localhost:5000

Environment Variables

Create a .env file in the project root:

VITE_API_URL=http://localhost:5000
VITE_APP_PORT=3000

VITE_API_URL is required for REST and GraphQL requests. The GraphQL client appends /graphql to this base URL.

VITE_APP_PORT is optional. If it is not set, Vite defaults to port 3000 in this project.

Getting Started

Install dependencies:

npm install

Start the Vite dev server:

npm run dev

Build for production:

npm run build

Preview the production build:

npm run preview

Quality Checks

Run tests once:

npm run test:run

Run tests in watch mode:

npm test

Run ESLint:

npm run lint

Format files with Prettier:

npm run format

Check formatting:

npm run format:check

Testing Approach

The project uses Vitest with jsdom and Testing Library. Tests cover utility behavior, API query generation, GraphQL API mapping, page behavior, and movie dialog/listing interactions.

Examples include:

  • src/features/movies/api/movie-graphql-api.test.ts
  • src/features/movies/components/MovieListings.test.tsx
  • src/features/movies/components/MovieUpsertDialog.test.tsx
  • src/features/movies/hooks/useUpsertMovie.test.tsx
  • src/features/movies/utils/buildMovieMutationPayload.test.ts
  • src/pages/MoviesPage.test.tsx

Notable Design Decisions

  • A single movie hook hides the REST/GraphQL transport choice from the UI.
  • Query keys include every input that can change the server response.
  • GraphQL selected fields are stored globally so column visibility and payload shape stay aligned.
  • REST query parameter construction is isolated in a utility function.
  • Mutations invalidate the movie query family instead of manually patching every possible paginated/filter cache entry.
  • Reference data such as genres is cached indefinitely.
  • Styling uses theme tokens so light and dark modes share the same component structure.

🧠 Production-Minded Engineering

This project emphasizes:

  • transport-agnostic frontend architecture
  • scalable server-state management
  • reusable UI patterns
  • predictable caching behavior
  • centralized API abstractions
  • typed frontend workflows
  • responsive dashboard interactions
  • separation of UI state and server state

The architecture is intentionally designed so the frontend can evolve independently from backend implementation details while maintaining a consistent user experience.

πŸ”— Related Projects

🧠 DevAssist-AI

Production-minded Retrieval-Augmented Generation (RAG) application using OpenAI embeddings, semantic retrieval, and centralized AI orchestration workflows. Project

ASP.NET Core Minimal API implementation built on top of the Postgres Movie Platform, exposing shared PostgreSQL functions through REST and GraphQL endpoints. Project

⚑ NodeMovieApi

Node.js + TypeScript API implementation using Express and GraphQL Yoga, demonstrating reusable backend architecture and centralized data access patterns. Project

Centralized PostgreSQL data platform featuring Dockerized infrastructure, overloaded SQL functions, reusable query architecture, and shared business logic powering multiple APIs and applications. Project

πŸ“¬ Contact

πŸ‘¨β€πŸ’» Author

Steven Wickers Senior Frontend Engineer

React β€’ TypeScript β€’ Node.js β€’ C# β€’ PostgreSQL β€’ Cloud

GitHub Description

React + TypeScript movie dashboard built with Vite, TanStack Query, TanStack Router, Axios, Redux Toolkit, Tailwind CSS, shadcn/ui, REST, and GraphQL.

About

React + TypeScript movie dashboard built with Vite, TanStack Query, TanStack Router, Axios, Redux Toolkit, Tailwind CSS, shadcn/ui, REST, and GraphQL

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages