Skip to content
Agentmart
Submit a tool

GraphQL Optimization

Techniques to solve the N+1 problem using Dataloader.

Skill by j4flmao

IndexedMIT★ 34
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/j4flmao-graphql-optimization

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Techniques to solve the N+1 problem using Dataloader.

Use cases: backend

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/graphql-optimization

What's inside

Skill name
GraphQL Optimization
Description
Techniques to solve the N+1 problem using Dataloader.
License
MIT
Instructions
39 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • name: only lowercase letters, digits and single hyphens allowed
  • name: "GraphQL Optimization" does not match folder "graphql-optimization"

Files (1)

  • SKILL.md

SKILL.md (first lines)

# GraphQL Optimization

## Purpose
GraphQL resolvers run per field, so a naive implementation issues one database query per parent object (N+1) and lets clients send arbitrarily expensive queries. This skill removes N+1 with batching, bounds query cost, and adds caching where it is safe.

## Agent Protocol

### Trigger
Exact user phrases: "graphql n+1", "dataloader", "graphql slow", "graphql performance", "batch resolvers", "query complexity", "depth limit", "persisted queries", "graphql caching".

### Input Context
- Server library (Apollo Server, GraphQL Yoga, Mercurius, graphql-java, Strawberry, gqlgen, Hot Chocolate).
- Data layer (SQL/ORM, REST backends) and the slow query or resolver.
- Whether the API is public (untrusted clients) or first-party only.

### Output Artifact
Loader definitions, context wiring, and server validation/caching config. No files unless requested.

### Response Format
```
Problem: {N+1 on Type.field | unbounded query | no caching}
Fix: {DataLoader keyed by X | depth<=N, complexity<=M | APQ/persisted | cache hint}
Queries before/after: {n} -> {m}
Code: {loader + resolver + server config}
```

## Diagnosing N+1
Enable SQL logging (or tracing via OpenTelemetry) and run a list query such as:

```graphql
{ posts(first: 50) { id title author { name } } }
```

51 queries (`SELECT posts` + 50 x `SELECT user WHERE id = ?`) is N+1. Target: 2 queries.

## DataLoader (Node.js)

Rules that make DataLoader correct:
1. **Create loaders per request** (in the context factory). A global loader caches across users and leaks data between requests.
2. The batch function receives an array of keys and **must return an array of the same length in the same order**, with `null` or an `Error` instance for missing keys.
3. Batching happens within one tick of the event loop; resolvers just call `loader.load(id)`.

```ts
import DataLoader from 'dataloader';
import type { Pool } from 'pg';

export interface User { id: string; name: string }

export function createLoaders(db: Pool) {
  return {
    userById: new DataLoader<string, User | null>(async (ids) => {
      const { rows } = await db.query<User>(
        'SELECT id, name FROM users WHERE id = ANY($1)',
        [ids as string[]],
      );
      const byId = new Map(rows.map((u) => [u.id, u]));
      return ids.map((id) => byId.get(id) ?? null);
    }),
    // One-to-many: key = authorId, value = that author's posts.
    postsByAuthor: new DataLoader<string, Post[]>(async (authorIds) => {
      const { rows } = await db.query<Post>(
        'SELECT * FROM posts WHERE author_id = ANY($1)',
        [authorIds as string[]],
      );
      const grouped = new Map<string, Post[]>();
      for (const p of rows) {
        const list = grouped.get(p.author_id) ?? [];
        list.push(p);
        grouped.set(p.author_id, list);
      }
      return authorIds.map((id) => grouped.get(id) ?? []);
    }),
  };
}

export interface Post { id: string; title: string; author_id: string }
```

Resolvers and per-request context (Apollo Server 4+ standalone):

```ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import depthLimit from 'graphql-depth-limit';
import { Pool } from 'pg';
import { createLoaders } from './loaders';
import { typeDefs } from './schema'; // SDL string or DocumentNode

const db = new Pool({ connectionString: process.env.DATABASE_URL });

type Ctx = { loaders: ReturnType<typeof createLoaders> };

const resolvers = {
  Post: {
    author: (post: { author_id: string }, _: unknown, ctx: Ctx) =>
      ctx.loaders.userById.load(post.author_id),
  },
  User: {
    posts: (user: { id: string }, _: unknown, ctx: Ctx) =>
      ctx.loaders.postsByAuthor.load(user.id),
  },
};

const server = new ApolloServer<Ctx>({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(8)], // reject deeply nested queries
});

await startStandaloneServer(server, {
  context: async () => ({ loaders: createLoaders(db) }), // fresh per request
});
```

Equivalents in other stacks: `java-dataloader` (graphql-java, Spring for GraphQL `@BatchMapping`), `strawberry.dataloader.DataLoader` (Python), `graph-gophers/dataloader` or gqlgen's dataloaden pattern (Go), Hot Chocolate `BatchDataLoader` (.NET).

## Alternative: Join-Based Resolution
When the parent resolver can see the selection set, fetch children in the same SQL with a JOIN (tools: Join Monster, PostGraphile, Hasura, Prisma `include`). This is faster for fixed shapes but couples resolvers to SQL; DataLoader stays simpler and works across data sources.

From j4flmao/agent-skills, MIT.

More from j4flmao/agent-skills

Signals

Stars
34
Forks
4
Open issues
0
Last commit (this folder)
Aug 30, 2026 (1 month ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from j4flmao

Cite this page

GraphQL Optimization skill. Agent Mart. https://agentmart.dev/skills/j4flmao-graphql-optimization (last updated 2026-10-24)