CineCircle uses an automated type generation system that creates frontend TypeScript types directly from backend type definitions. This eliminates manual type duplication and ensures frontend types always match the backend API contract.
Backend Types (TypeScript)
↓
JSON Schema (auto-discovered)
↓
OpenAPI 3.0 Spec
↓
Frontend Types (auto-generated)
-
Type Discovery (
npm run types:discover)- Scans
backend/src/types/apiTypes.tsandmodels.ts - Extracts all exported types and interfaces
- Generates JSON schemas using
ts-json-schema-generator - Outputs to
backend/src/docs/schemas.json
- Scans
-
OpenAPI Generation (
npm run types:openapi)- Reads the JSON schemas
- Combines them with auto-detected Express routes
- Generates OpenAPI 3.0 spec with full schemas
- Outputs to
backend/src/docs/openapi.json
-
Frontend Type Generation (
npm run types:frontend)- Reads the OpenAPI spec
- Generates TypeScript types using
openapi-typescript - Copies to
frontend/types/api-generated.ts
From the project root:
npm run backend:typesThis runs all three steps in sequence inside the Docker container.
Run type generation whenever you:
- Add a new API endpoint
- Modify an existing type in
backend/src/types/ - Change request/response structures
- Add new fields to models
// frontend/services/moviesService.ts
import { api } from "./apiClient";
import type { components } from "../types/api-generated";
// Extract types from the generated schema
type GetMovieEnvelope = components["schemas"]["GetMovieEnvelope"];
type UpdateMovieInput = components["schemas"]["UpdateMovieInput"];
export function fetchMovie(tmdbId: string) {
return api.get<GetMovieEnvelope>(`/movies/${tmdbId}`);
}
export function updateMovie(movieId: string, payload: UpdateMovieInput) {
return api.put<GetMovieEnvelope>(`/movies/cinecircle/${movieId}`, payload);
}// frontend/components/MovieCard.tsx
import type { components } from "../types/api-generated";
type Movie = components["schemas"]["Movie"];
export function MovieCard({ movie }: { movie: Movie }) {
return <div>{movie.title}</div>;
}- Add controller function
- Add route
- Update
backend/src/types/models.ts - Update
backend/src/types/apiTypes.ts - Update
backend/src/types/endpoints.ts - Manually copy type to
frontend/types/models.ts - Manually copy to
frontend/types/apiTypes.ts - Manually copy to
frontend/types/endpoints.ts - Update frontend service
- Add tests
- Update Prisma schema (if needed)
- Add controller function with transformation (e.g.,
mapMovieDbToApi) - Add route in
routes/index.ts - Add/update type in
backend/src/types/models.tsorapiTypes.ts - Add tests
- Run
npm run backend:types - Update frontend service (types auto-imported)
Controllers must transform Prisma types to API types:
// backend/src/controllers/tmdb.ts
import type { Movie } from "../types/models";
export function mapMovieDbToApi(dbMovie: Prisma.movieGetPayload<{}>): Movie {
return {
movieId: dbMovie.movieId,
title: dbMovie.title,
description: dbMovie.description,
languages: dbMovie.languages ? (dbMovie.languages as string[]) : null,
imdbRating: dbMovie.imdbRating != null ? Number(dbMovie.imdbRating) : null,
localRating: dbMovie.localRating,
numRatings: dbMovie.numRatings,
};
}
export const getMovieById = async (req: Request, res: Response) => {
const movie = await prisma.movie.findUnique({ where: { movieId } });
const movieResponse = mapMovieDbToApi(movie); // ← Transform
res.json({ message: "Movie found", data: movieResponse });
};This ensures:
- Prisma DB types → API contract types properly converted
- Frontend receives data in expected format
- Type mismatches caught at compile time
Prisma schema types don't always match API types:
| Prisma Schema | API Type | Reason |
|---|---|---|
BigInt |
number |
JavaScript number limit |
Json |
string[] |
Type-safe arrays |
String |
number |
Numeric ratings as strings in DB |
Transformations bridge this gap.
Solution: Generate the types:
npm run backend:typesCauses:
- Type not exported from
apiTypes.tsormodels.ts - Type uses unsupported TypeScript features
- Type discovery skipped it (check logs)
Solution:
- Ensure type is exported:
export type X = ... - Check
backend/src/docs/schemas.jsonto see if it was generated - Run
npm run types:discoverand check for warnings
Solution:
npm run backend:startSymptoms:
- Runtime errors about missing fields
- TypeScript errors in frontend
Solution:
npm run backend:typesbackend/
src/
types/
models.ts <- Define domain models here
apiTypes.ts <- Define request/response types here
docs/
schemas.json <- Auto-generated JSON schemas
openapi.json <- Auto-generated OpenAPI 3.0 spec
api-types.ts <- Auto-generated TypeScript types
scripts/
generate-types.js <- Type discovery script
generate-docs.js <- OpenAPI generation script
generate-frontend-types.js <- Frontend type generation script
frontend/
types/
api-generated.ts <- Auto-generated (copied from backend)
models.ts <- UI-specific types only
services/
moviesService.ts <- Uses generated types
userService.ts <- Uses generated types