Summary: A Cheat Sheet of Every Important GraphQL Pattern From This Series
Summary: A Cheat Sheet of Every Important GraphQL Pattern From This Series
~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
26 chapters, one continuous project, a complete events GraphQL API from the first schema declaration through to ACL and batch resolvers. This final chapter bundles the most important patterns as a reference for your own practice - no new content, just condensation.
Schema skeleton
type Query {
myField(arg: String): MyType
@resolver(class: "Vendor\\Module\\Model\\Resolver\\MyField")
@doc(description: "...")
}
type Mutation {
myMutation(input: MyMutationInput!): MyMutationOutput
@resolver(class: "Vendor\\Module\\Model\\Resolver\\MyMutation")
}
extend type ExistingCoreType {
my_extra_field: String @resolver(class: "...")
}Resolver skeleton
final class MyField implements ResolverInterface
{
public function __construct(
private readonly MyDataProvider $dataProvider,
) {
}
public function resolve(
Field $field,
$context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): array {
return $this->dataProvider->getData($args);
}
}The four GraphQL exceptions
GraphQlInputException- business-invalid input.GraphQlAuthorizationException- missing permission.GraphQlNoSuchEntityException- entity doesn't exist.GraphQlAlreadyExistsException- unique value already taken.- Always with
__('...')instead of a raw string (translatable).
Authentication check in a resolver
if ($context->getUserType() !== UserContextInterface::USER_TYPE_CUSTOMER
|| !$context->getExtensionAttributes()->getIsCustomer()
) {
throw new GraphQlAuthorizationException(__('...'));
}
$customerId = (int) $context->getUserId();Batch resolver against N+1
final class MyBatchField implements BatchResolverInterface
{
public function resolve(array $requests): BatchResponse
{
$response = new BatchResponse();
// collect all IDs from $requests, ONE query, map results back
foreach ($requests as $request) {
$response->addResponse($request, /* ... */ null);
}
return $response;
}
}The ten golden rules of this series
- Run
bin/cache-clean configimmediately after everyschema.graphqlschange - nosetup:di:compileneeded, but always a cache clean. - Keep resolvers thin, let DataProviders carry the actual logic - but only when the logic justifies it (chapter 15).
- Extend existing core types with
extend typeinstead of duplicating new, isolated types. - Reuse existing core input types (
FilterTypeInput,SortEnum) instead of reinventing your own. - Use Non-Null (
!) only for fields that genuinely can never be missing on the business level - null bubbling otherwise takes down entire response branches. - DataProviders use repositories, not bare collections -
addFieldToFilter()always in array form['eq' => $value]. - Always throw one of the four GraphQL exceptions for error cases, never a generic exception that gets masked in production mode.
- Use
getExtensionAttributes()->getIsCustomer()instead of a plaingetUserId() > 0check for reliable customer detection. - For fields with foreseeably large lists, consider
BatchResolverInterfaceoverResolverInterfacefrom the start. - For any problem, check cache clean first, then introspection, then developer mode and
exception.log- only suspect your own code after that.
Tipp: The complete Mironsoft\Event module built across this series covers every one of these patterns on a single, consistent API - when in doubt about a specific pattern, it's worth jumping back to the relevant chapter of this series rather than looking the pattern up in isolation.
That wraps up this series. From the first question, "What even is GraphQL?", to a complete, performant, tested, and secured events API - the patterns shown here carry over just as well to any other custom GraphQL project in Magento 2.