Client GraphQL
The client application uses Apollo Client to communicate with the backend GraphQL API. All operations are defined centrally, and TypeScript types are generated automatically from the backend schema.
Operations
All GraphQL operations are defined centrally:
| File | Contents |
|---|---|
client/src/graphql/fragments.ts | Reusable fragments (currentUserFields, relatedUserFields, etc.) |
client/src/graphql/queries.ts | All queries (CurrentUser, GetSubmission, etc.) |
client/src/graphql/mutations.ts | All mutations (Login, CreateSubmissionDraft, etc.) |
Operations are defined using graphql-tag's gql template literals and exported as constants (e.g., CURRENT_USER, LOGIN). Fragments are interpolated into operations using template literal expressions.
TypeScript Code Generation
We use @graphql-codegen to generate TypeScript types from the backend schema and the client's operation files. This provides typed interfaces for query results, mutation responses, and variables without changing existing useQuery/useMutation call sites.
Generated Output
Codegen produces client/src/graphql/generated/graphql.ts containing:
- Schema types:
User,Submission,Publication, enums likeSubmissionStatus, input types, etc. - Operation types:
CurrentUserQuery,LoginMutation,CreateSubmissionDraftMutationVariables, etc. - Fragment types:
currentUserFieldsFragment,relatedUserFieldsFragment, etc.
This file is gitignored — it's a derived artifact regenerated from the schema and operation files.
How Generated Types Are Named
The generated type name is derived from the operation name inside the gql document, not from the JavaScript constant:
// The constant name doesn't matter for codegen — the operation name does:
export const CURRENT_USER = gql`
query CurrentUser { // ← generates CurrentUserQuery
currentUser { ... }
}
`
export const LOGIN = gql`
mutation Login($input: LoginInput!) { // ← generates LoginMutation
login(input: $input) { ... } // and LoginMutationVariables
}
`Codegen appends Query or Mutation to the operation name to produce the result type, and adds Variables for any operation that accepts arguments.
Operation names must be unique
Codegen generates types in a single file, so every operation name across all query and mutation files must be unique. If two operations share a name, codegen will silently merge or overwrite their types.
How It Works
The codegen config is in client/codegen.ts. By default, it introspects the running backend at http://pilcrow.lndo.site/graphql to get the compiled schema, then generates types and an updated schema.graphql file for all operations found in client/src/graphql/**/*.ts.
Automatic Regeneration (Dev Server)
During development, types are regenerated automatically when:
- Dev server starts: Introspects the backend and generates types
- Client operations change: Editing
queries.ts,mutations.ts, orfragments.tstriggers regeneration - Backend schema changes: Editing
.graphqlfiles inbackend/graphql/triggers re-introspection and regeneration - Build: Types are generated from the committed
schema.graphqlfile (no backend needed)
If the backend is not running when the dev server starts, throwOnStart: false allows the dev server to start anyway using previously generated types.
TIP
The compiled schema file (client/src/graphql/schema.graphql) is committed to the repository. During development, codegen introspects the live backend and writes both the types and an updated schema file automatically. During builds, types are generated from this committed schema file so the backend is not required. If you need to update the schema file manually (e.g., without the dev server running), use lando yarn graphql:codegen-offline.
Manual Commands
Run these from the project root using lando yarn:
# Run codegen using introspection (requires lando running)
lando yarn graphql:codegen
# Export schema then generate types from the file (offline/CI)
lando yarn graphql:codegen-offline
# Export compiled schema only
lando yarn graphql:fetch-schemaYou can override the schema source with an environment variable. This is useful for CI workflows where introspection isn't available:
GRAPHQL_SCHEMA=src/graphql/schema.graphql lando yarn graphql:codegenUsing Generated Types
See TypeScript Conventions for patterns on using generated types in components and tests, including typed mock responses, derived types, and boundary casting.
Configuration
The codegen configuration in client/codegen.ts uses:
schema-astplugin — writes the introspected schema toschema.graphql(keeps the committed file in sync with the backend)typescriptplugin — generates base types from the schematypescript-operationsplugin — generates result/variable types for each operationsort: trueonschema-ast— ensures consistent ordering across environmentsnamingConvention: "keep"— preserves snake_case field names from the Laravel backendmaybeValue: "T | null | undefined"— nullable fields can benullorundefined