Introduction to GraphQL usage in the axon911-frontend monorepo
The axon911-frontend monorepo uses a unified GraphQL code generation approach to ensure type safety, maintainability, and developer productivity across all frontend applications.
Our GraphQL implementation follows these key principles:
@prepared911/data-gql packagepnpm lint:graphql (part of pnpm check) checks document layout and, with graphql-eslint, each document against the schema; oxlint rules cover how TypeScript uses ApolloBefore diving into GraphQL in this monorepo, you should be familiar with:
useQuery, useMutation)If you're new to any of these, consider reviewing the relevant documentation first.
This GraphQL documentation is organized to follow a natural learning progression:
Each section builds on the previous one, so we recommend reading them in order.
The GraphQL schema serves as the single source of truth for both backend and frontend. Types, enums, and interfaces are all generated from this schema, ensuring consistency across the stack.
Using the near-operation-file preset, generated types are placed in the same folder as your .graphql files:
A document file is named queries.graphql, mutations.graphql, subscriptions.graphql, or fragments.graphql, holds only that kind of definition, and lives under src/ of an app, remote, or package. pnpm lint:graphql fails otherwise, including for a file in a remote's exposes/ directory, which codegen never reads.
Instead of auto-generated hooks, we use typed document nodes with Apollo Client's useQuery, useMutation, and useSubscription hooks. This provides better flexibility and type safety.
Create a queries.graphql file next to your component:
pnpm dev (and the shell variants) run codegen once, when they start. It does not watch: after you add or edit a .graphql file, regenerate the types yourself:
These rules help maintain clean, efficient GraphQL operations and prevent over-fetching.
✅ Do this:
❌ Don't do this:
✅ Do this:
❌ Don't do this:
The TypeScript compiler catches a wrong variable type, such as limit: "20". It does not catch a magic string for an enum: with enumsAsConst, the enum type is a union of string literals, so "ACTIVE" type-checks. Use the generated const (ChatroomStatus.Active) in review; there is no lint rule for it yet.
Understanding where to import GraphQL-related types and values is crucial for maintaining proper code organization and leveraging the benefits of colocation. Our setup uses two primary import sources, each serving a specific purpose.
The centralized @prepared911/data-gql package contains shared, reusable types that are used across multiple components and operations:
Enums - Type-safe constants for GraphQL enum values
Input types - Types for mutation and query variables
Scalar types - Custom scalar types defined in the GraphQL schema
Shared type system types - GraphQL infrastructure types that are reused across operations
Factory functions (for testing) - Test data factories
These types are centralized because they represent shared concepts that don't belong to any single component or operation.
Component-level GraphQL files (colocated with your components) export operation-specific types and values:
Document nodes - Typed document nodes for use with Apollo hooks
Operation result types - Query, Mutation, and Subscription return types
Variables types - Operation-specific variable types
Fragment types - Component-specific fragment types
These are colocated because they represent the data contract for a specific component or operation, making it easy to see what data a component needs.
Here's a practical example showing both import sources used together:
This import strategy provides several key benefits:
pnpm lint:graphql runs as part of pnpm check. See "GraphQL gate" in LINT-POLICY for the full rule list. In short: documents are in the right place with the right name; every operation is named and validated against the schema (no unused variables, no deprecated fields, id selected where the type has one); and TypeScript does not use inline gql, explicit hook type arguments, or indexed access on generated result types. There is no suppressions file: fix the document.
On this page