gqlts builds GraphQL queries with TypeScript instead of loose strings. It is a small example of a recurring preference: move mistakes to the editor when the runtime failure would be boring and avoidable.
Why it exists#
GraphQL gives teams a strong contract, then a lot of codebases throw part of that contract away by building queries as strings. That works until a field changes, a nested selection drifts from the type shape, or a caller ships a query that only fails after the request leaves the process.
This repo came from wanting the query shape closer to the language. Not a giant client framework. Just a small builder that makes field selection explicit and lets TypeScript catch the boring mistakes before CI or production has to.
The pattern#
- Keep the query shape close to the type shape.
- Make field selection explicit.
- Avoid hiding GraphQL behind a giant abstraction.
- Let generated or inferred types do the boring checking.
- Keep the runtime client boring: send the operation, keep
{ data, errors, extensions }visible, and let callers decide how strict the product boundary should be. - Support subscriptions, uploads, batching, and request options without making every app invent a second client beside GraphQL.
Where it earns its weight#
The repo has a CLI/runtime split, which is the right shape for this problem. Generation belongs near the schema and build loop. Runtime code belongs in the app, where latency, headers, retries, and errors have product meaning.
That split is the part I still like. It keeps schema drift out of hand-written query strings without pretending the generated client owns every production concern. Authentication, caching, retry policy, and observability still belong to the app using the client.
What I still like about it#
The useful boundary is restraint. A typed query builder can become a private language very quickly. The better version stays close to GraphQL itself, keeps the output understandable, and does not prevent engineers from reading the query they are sending.
That is the engineering principle I would carry into a production API client today: generate what removes drift, type what prevents avoidable defects, and keep the abstraction thin enough that debugging still feels like software engineering rather than archaeology.