agentsclimarketplace

Graphql

Skill api-platform/skillset/skills/graphql

API Platform agent skills

Install
npx -y skills add api-platform/skillset --skill graphql

Assembled 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.
  • 24 stars24 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

Exposes API Platform resources over GraphQL — enabling GraphQL, Query/QueryCollection/Mutation/DeleteMutation operations, security expressions, custom resolvers, Relay cursor pagination, and nested relations. Use when the user mentions GraphQL, a GraphQL schema, queries/mutations, Relay connections, GraphQL playground, resolvers, or asks to expose existing REST resources via GraphQL.

SKILL.md

7.2 KB, as published. Nobody here has run it

GraphQL

Default to REST. GraphQL trades away things API Platform gives you for free over REST: HTTP cache semantics (ETag, Cache-Control, invalidation), one URL per resource, simple CDN/proxy caching, and predictable per-operation cost. A single GraphQL query can fan out into arbitrarily deep/expensive resolution. Reach for GraphQL when it is a hard client requirement (e.g. a Relay/Apollo frontend, or clients that genuinely need to select fields and avoid round-trips) — not as a default. The same resource class can serve both; you don't have to choose globally.

Enabling GraphQL

Install api-platform/graphql (composer require api-platform/graphql), then it's on. A /graphql endpoint and the GraphiQL playground (/graphql/graphiql) appear. Disable globally or per resource as needed:

# config/packages/api_platform.yaml
api_platform:
    graphql:
        enabled: true
        graphiql:
            enabled: true

Declaring GraphQL operations

GraphQL operations live in graphQlOperations and are separate classes from the REST ones, under ApiPlatform\Metadata\GraphQl\. A resource with no graphQlOperations still gets a default set (item query, collection query, create / update / delete mutations) once GraphQL is enabled.

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\DeleteMutation;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;

#[ApiResource(graphQlOperations: [
    new Query(),
    new QueryCollection(),
    new Mutation(name: 'create'),
    new Mutation(name: 'update'),
    new DeleteMutation(name: 'delete'),
])]
class Book {}

Mutation and DeleteMutation require a name — it becomes the GraphQL field name (createBook, updateBook, deleteBook). Query and QueryCollection only need a name when you declare more than one of the same kind (e.g. a custom query alongside the default).

Security on GraphQL operations

Same ExpressionLanguage as REST (see operations), set per GraphQl operation:

new Query(security: "is_granted('ROLE_USER')")
new Mutation(name: 'update', security: "object.getOwner() == user")
new Mutation(name: 'update', securityPostDenormalize: "object.getOwner() == user")

Because one GraphQL query can traverse relations, securing only the top-level operation is not enough — guard the related resources' operations too, or a nested field becomes an unguarded read path.

Custom resolvers

Use a resolver when a query/mutation needs logic beyond fetch-by-id. Resolvers are services implementing one of:

  • QueryItemResolverInterface__invoke(?object $item, array $context): object
  • QueryCollectionResolverInterface__invoke(iterable $collection, array $context): iterable
  • MutationResolverInterface__invoke(?object $item, array $context): ?object

Query arguments arrive in $context['args'].

use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;

final class BookResolver implements QueryItemResolverInterface
{
    public function __invoke(?object $item, array $context): object
    {
        // $item is the fetched Book (or null if read: false); enrich or replace it
        return $item;
    }
}

With Symfony autoconfiguration the resolver is wired automatically. Without it, tag the service api_platform.graphql.query_resolver (or ..._mutation_resolver). Then reference it by class name on the operation, and set read: false when the resolver should fetch the data itself rather than receiving a hydrated item:

new Query(name: 'recommended', resolver: BookResolver::class, read: false)
new Query(
    name: 'search',
    resolver: BookResolver::class,
    args: [
        'query' => ['type' => 'String!', 'description' => 'Full-text search'],
        'limit' => ['type' => 'Int'],
    ],
)

args overrides the auto-generated argument set — define it when the query takes parameters that aren't resource fields.

Relations and the N+1 trap

GraphQL embeds related resources by selecting nested fields:

{
  book(id: "/books/1") {
    title
    author { name }
  }
}

Relations resolve through the same providers as REST. A deeply nested query can trigger many small queries (the classic N+1). API Platform mitigates common cases, but verify against real queries and add Doctrine joins / a custom collection provider where a hot path fans out. This open-ended cost is the main reason REST is the safer default for cache-sensitive APIs.

Pagination: Relay cursor connections

Collection queries return Relay-style cursor connections by default (edges { node { ... } cursor }, pageInfo, totalCount), driven by first/ after/last/before arguments:

{
  books(first: 10, after: "endCursor") {
    totalCount
    edges { node { title } cursor }
    pageInfo { endCursor hasNextPage }
  }
}

To use simple page-based pagination instead, set paginationType: 'page' on the resource or the QueryCollection:

use ApiPlatform\Metadata\GraphQl\QueryCollection;

#[ApiResource(graphQlOperations: [
    new QueryCollection(paginationType: 'page'),
])]
class Book {}

All the pagination* controls from pagination (items per page, max, partial) apply to GraphQL collections too.

Real-time subscriptions

When a resource has mercure: true, GraphQL subscription operations push updates through the Mercure hub — see mercure.

Laravel

GraphQL is supported on Laravel. The operation classes (Query, QueryCollection, Mutation, DeleteMutation), security expressions, Relay connections, paginationType, custom resolvers and the N+1 caveats are all the same. Differences:

  • Install composer require api-platform/graphql, then enable it in config/api-platform.php under graphql ('enabled' => true) — not YAML. Depth/ complexity limits and graphiql live in the same config block.
  • Resolvers implement the same QueryItemResolverInterface / QueryCollectionResolverInterface / MutationResolverInterface and are referenced by class-string on the operation; Laravel's container resolves them — there are no api_platform.graphql.*_resolver tags to apply.
  • Real-time subscription operations depend on Mercure, which has no Laravel integration (see mercure), so GraphQL subscriptions are effectively Symfony-only.

Checklist

  • GraphQL chosen for a real client requirement, not as a REST default
  • Every Mutation/DeleteMutation has a name
  • security set on nested resources, not just the entry-point operation
  • Custom resolvers tagged (or autoconfigured) and referenced by class name
  • read: false set when the resolver fetches its own data
  • Deep/nested queries checked for N+1; joins added on hot paths
  • paginationType: 'page' set only if the client doesn't want Relay connections

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.