GraphQL
Refract’s API is GraphQL end to end: Apollo Server on the backend, Apollo Client in the portal, and a small codegen loop so the frontend never guesses field shapes. Resolvers stay thin; utilities own the rules; the schema file is generated so humans and agents share one contract. Once you internalize that pipeline, adding a query or mutation becomes a boring checklist—which is exactly what you want at 2 a.m. when shipping a hotfix.How it works
- You write a resolver in
apps/backend/src/gql/. - Running
make gql-codegendumps the full schema to/shared/template.schema.graphql. - The frontend codegen reads that schema plus any
useQuery/useMutationcalls in the frontend source, and generatesapps/portal/src/gql/hooks.ts— a fully typed file of React hooks. - Your React component imports and calls the generated hook. TypeScript ensures the component’s types stay in sync with the API.
codegen-verification job and make local-verification both enforce this — if you forget to run codegen after a schema change, they fail and tell you to commit the updated generated file.
Resolver structure
Resolvers in Refract follow a consistent shape. Here’s a real example — a query that fetches countries with Redis caching:- The resolver function (named after the operation, e.g.
fetchCountries) - A schema builder function (named
build<OperationName>Schema) - The parameter type, exported so tests can import it instead of redefining it
Middleware chain
Resolvers useresolveWithMiddlewares to enforce authentication and authorization before running business logic. A typical secured mutation looks like:
Mutation return types
Mutations always return a discriminated union:result field with whatever data the client needs.
⚠️ Watch out: the resolver’s return shape must match the GraphQL SDL response fields exactly. For example, if your resolver returnsreason: string, your SDL must typereasonasGraphQLString(not a list).
Adding a new query or mutation
-
Create the resolver file
For a query:
apps/backend/src/gql/queries/fetchMyThing.tsFor a mutation:apps/backend/src/gql/mutations/createMyThing.tsExport the resolver function, the schema builder, and the params type: -
Add business logic to a utility
apps/backend/src/utilities/myThing.ts— pure function, no direct SDK calls:💡 Tip: for input normalization and validation, keep pure helpers colocated with the operation (e.g. inside
apps/backend/src/gql/mutations/<operationName>.ts) or in stableapps/backend/src/utilities/*. Avoid importing helpers from a mutation folder you plan to delete during extraction. -
Register the schema builder
In
apps/backend/src/gql/index.ts, add your builder to the merged schema:💡 Tip: schema registration is a hard gate. Don’t delete or rename the previous operation module until you’ve updated
apps/backend/src/gql/index.tsandmake gql-codegensucceeds. -
Run codegen
This generates the updated
apps/portal/src/gql/hooks.tswith a newuseFetchMyThingQueryhook. -
Use the hook in the frontend
-
Write a test
apps/backend/src/gql/queries/__tests__/fetchMyThing.spec.ts: -
Verify
💡 Tip: after running
make gql-codegen, sanity-checkapps/shared/template.schema.graphqlto ensure your operation field names (e.g.updateMailingListContact) appear exactly once. This catches accidental SDL duplication that unit tests alone might not reveal.
What’s next?
- Architecture Overview — where GraphQL fits in the full request lifecycle.
- Tooling System — how the
toolsobject gets intocontext. - Testing — how to test resolvers and utility functions.
