Skip to content

Conversation

ronjalh
Copy link
Member

@ronjalh ronjalh commented Oct 16, 2026 •

Built on #66. This PR is based on Xenia's branch 26-set-up-the-database-on-the-vm and targets it, so only my changes show here. Merge #66 first, then change the base of this PR to dev.

Summary

Adds the GraphQL server, so the catalog and the search can be used through GraphQL. Until now the schema, the database, the seed and the search function existed, but nothing could call them. This is the main part of hand-in 2 ("a running backend and a client with some interaction").

The user data and the login are not part of it. Those operations answer a clear error until they are built (see below).

What is in it

  • Server. Apollo Server 5 on Express 5, schema first (it loads backend/src/schema.graphql). Port 3001, endpoint /graphql. npm run backend (restarts on changes) or npm run start -w backend.
  • Catalog resolvers. apparatus, elementGroup, element, vault, catalogItem and searchCatalog as a connection. CatalogItem is resolved to Element or Vault, imageUrl is built from IMAGE_BASE_URL, and variants lists the other variants of a code that are not removed.
  • DataLoader for the element or vault behind each search result, for the variants and for the apparatus. A page of search results costs a fixed number of queries (4–6 in the tests) whether it has 5 or 100 results.
  • totalCount only when the query asks for it, also through fragments, and not when @skip or @include leaves it out. A client writes totalCount @include(if: $firstPage).
  • Errors. BadUserInputError becomes BAD_USER_INPUT. An unexpected error gives "Internal server error" and no details (they go to the log). A depth limit of 8 levels rejects a query before it runs (GRAPHQL_VALIDATION_FAILED); the introspection fields are not counted, so Apollo Sandbox still works.
  • Also served: /images/<apparatus>/<file> for the drawings (kept 30 days by the browser; Apache will serve them on the virtual machine) and /health. A request body above 100 kB is refused, and CORS only lets the addresses in CORS_ORIGIN call the API.
  • Not built yet. me returns null. gymnast, routine and every mutation that needs a coach answer UNAUTHENTICATED. login and calculateDScore answer NOT_IMPLEMENTED, a temporary code that goes away when they are built. A test checks that every mutation in the schema has a resolver.
  • Settings: PORT, IMAGE_BASE_URL and CORS_ORIGIN in backend/.env.example.
  • Docs. A "GraphQL server" section in the README (how to start it and try a query) and a section "The server" in docs/API.md with the decisions. docs/AI_USAGE.md has an entry.

What changed because of #66

#66 has a first server (backend/src/index.ts, backend/src/resolvers.ts) and some scripts. This PR keeps everything that is not the same server twice, and the work in #66 is kept where it can be:

  • Kept from 26 set up the database on the VM #66: the setup script (migrations, then the seed; I ran it on an empty database and it works), the build script, Prisma ^7.10.1, base: '/project2/' in vite.config.ts and the packages that were added at the root.
  • The entry point is still backend/src/index.ts (the name from 26 set up the database on the VM #66, so dev and setup keep their meaning), now with the full server. The first resolvers in backend/src/resolvers.ts are replaced by backend/src/server/resolvers/, which resolve CatalogItem to Element or Vault as the schema needs.
  • start runs tsx src/index.ts and not node dist/index.js: backend/tsconfig.json has noEmit, so there is no dist/ to run.
  • The default port is 3001 (it was 4000), as the assignment says for the virtual machine.
  • @prisma/adapter-pg is moved to ^7.10.1 and dotenv to ^18.0.8 in backend/package.json, so each package has one version across the root and the backend (Prisma recommends the same version for prisma, @prisma/client and the adapter).

Choices

Question Choice Why
graphql version pinned to 16 Apollo Server 5 needs ^16, and the latest release is 17
tsx and dotenv regular dependencies The server runs with tsx and loads backend/.env. Install with npm ci, not --omit=dev
variants the other variants, without the item The count of variants is variants.length + 1 (this settles the question in #58)
Reading the original error originalError directly Apollo's unwrapResolverError fails when two copies of graphql are loaded (found by a test)

Testing

  • npm run lint, npm run typecheck -w backend, npm run build and npm test pass on the merged result: 105 tests, 60 of them in backend/src/server/, including the database tests against the seeded catalog.
  • npm run setup -w backend (from 26 set up the database on the VM #66) creates the tables and seeds an empty database, and npm run start -w backend then starts the server from index.ts: /health answers, and a search for "hecht" gives 24 results.
  • The server tests cover: the catalog queries, the type of each item, variants, the search compared with the search function (same page, cursors and count), following the cursors to the end, filters and sort, the number of database queries, totalCount only when asked, every BAD_USER_INPUT case, the depth limit, introspection, hidden details of an unexpected error, the operations that are not built, and HTTP (health, a query, a drawing and its cache header, 404, a path outside the images folder, CORS, a body above 100 kB).
  • I also started the server and called it over HTTP: a search for "hecht" gives 24 results with variants, image addresses, group and apparatus.

Notes

Closes #28

🤖 Generated with Claude Code

Apollo Server 5 on Express 5, schema first. Resolvers for the apparatus, groups, elements, vaults and the search, with DataLoader, a depth limit, error mapping and the settings PORT, IMAGE_BASE_URL and CORS_ORIGIN. The user data and the login answer clear errors until they are built.
@ronjalh ronjalh requested a review from a team as a code owner October 16, 2026 16:21
@ronjalh ronjalh self-assigned this Oct 16, 2026
Keeps the work of #66 (the setup script, the build and start scripts, Prisma 7.10.1, base /project2/ in Vite). The server entry point is now backend/src/index.ts, which replaces the first version, and the resolvers of the first version are replaced by backend/src/server/resolvers/. start runs tsx, because there is no dist/.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mask unhandled internal errors.
Non-client errors currently fall through to return formatted, which leaks raw internal exception messages and stack traces to the client. We should catch non-client, non-GraphQL errors and mask them to "Internal server error".

Base automatically changed from 26-set-up-the-database-on-the-vm to dev October 20, 2026 17:41
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants