Graphql for mobile
Skill almasumdev/awesome-mobile-backend-agent-skills/.github/skills/api/graphql-for-mobile
Agent skills for the backend-for-mobile layer: APIs, auth, push, sync, and BaaS integrations.
npx -y skills add almasumdev/awesome-mobile-backend-agent-skills --skill graphql-for-mobileAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
GraphQL server design tuned for mobile -- persisted queries, batching, N+1 mitigation, and Apollo client integration. Use when building or reviewing a GraphQL API for mobile apps.
SKILL.md
5.1 KB, as published. Nobody here has run it
GraphQL for Mobile
Instructions
GraphQL's value for mobile is tight payload shaping and cross-resource composition in one round-trip. Its risks are unbounded queries, N+1 database loads, and shipping raw queries from the app. Mitigate all three.
1. Schema Design
Favor a graph of small, stable types over RPC-style fields.
type Article {
id: ID!
title: String!
summary: String
author: User!
comments(first: Int = 20, after: String): CommentConnection!
publishedAt: DateTime!
}
type Query {
article(id: ID!): Article
feed(first: Int = 20, after: String, filter: FeedFilter): ArticleConnection!
}
Use Relay-style connections (Connection, Edge, PageInfo) for all lists. Cursors are opaque, page size defaults to 20, maxes at 100.
2. Persisted Queries
Do not let the mobile client send arbitrary GraphQL strings in production. Use persisted queries: at build time, every query is hashed and registered on the server; the app sends only the hash.
POST /graphql
{"id": "sha256:ab12...", "variables": {"id": "a_1"}}
Benefits: smaller requests, query allowlisting, CDN-cacheable reads (GET with hash), no accidental schema exploration from shipped binaries.
Apollo iOS / Apollo Kotlin generate persisted IDs via their build plugins; wire them into the pipeline.
3. N+1 Mitigation with Dataloader
Resolvers that fetch child objects trigger one DB hit per parent unless batched.
// Node/TS with dataloader
const userLoader = new DataLoader<string, User>(async (ids) => {
const users = await db.users.findMany({ where: { id: { in: ids as string[] } } });
const byId = new Map(users.map(u => [u.id, u]));
return ids.map(id => byId.get(id)!);
});
const resolvers = {
Article: {
author: (article, _, ctx) => ctx.loaders.user.load(article.authorId),
},
};
Every request builds a fresh loader set (never share across requests -- caches must not leak between users).
4. Query Cost and Depth Limits
Reject expensive queries before resolving them.
- Max depth: 8–10. Reject deeper.
- Max complexity: compute a weighted cost (lists multiply by
first). - Max aliases / duplicate fields: cap to prevent amplification attacks.
Libraries: graphql-depth-limit, graphql-query-complexity (Node); graphql-java cost analyzer.
5. Mutations
Mutations take a single input object and return a typed payload with a result union or an errors list:
input CreateCommentInput { articleId: ID!, body: String!, clientMutationId: String }
type CreateCommentPayload { comment: Comment, userErrors: [UserError!]! }
type Mutation { createComment(input: CreateCommentInput!): CreateCommentPayload! }
userErrors surfaces validation errors without failing the whole HTTP request. HTTP 200 is returned; only infrastructure failures become non-200. Include clientMutationId to pair optimistic UI updates with server responses.
6. Subscriptions
Prefer WebSockets (graphql-ws) for realtime. Keep subscription payloads small and resumable via a lastEventId. For most mobile apps, polling or periodic refetch is simpler than subscriptions -- reach for subs only when sub-second latency matters (chat, live scores, collaborative edit).
7. Caching
- HTTP-level: persisted-query GETs are CDN-cacheable by query hash + variables hash.
- Client-level: Apollo Normalized Cache keyed by
__typename:id. Every type that can appear in multiple queries must exposeidand__typename. - Ensure stable
idfields; never reuse ids across types without a composite key.
8. Apollo Client on Mobile
Apollo Kotlin (Android):
val apollo = ApolloClient.Builder()
.serverUrl("https://api.example.com/graphql")
.autoPersistedQueries() // sends hash first, falls back to full query once
.normalizedCache(MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024))
.addHttpInterceptor(AuthHeaderInterceptor(tokenStore))
.build()
val feed = apollo.query(FeedQuery(first = 20)).fetchPolicy(FetchPolicy.CacheAndNetwork).execute()
Apollo iOS has an equivalent API (ApolloClient, NormalizedCache, RequestChainNetworkTransport).
9. Error Handling
GraphQL returns 200 even for partial failures. The client must inspect both errors (top-level GraphQL errors) and data. Map stable error codes through extensions.code so clients can act (UNAUTHENTICATED, RATE_LIMITED, VALIDATION_FAILED).
Checklist
- Relay-style connections for every list field with default/max page sizes.
- Persisted queries enforced in production; raw queries rejected.
- Dataloader (or equivalent) batching for every parent-to-child field.
- Depth and complexity limits enabled and tested.
- Mutations follow
input/payload+userErrorspattern. - Every cacheable type exposes
id+__typename. - Error codes in
extensions.codeare stable and documented. - Apollo client configured with auth interceptor, cache size, and fetch policy.