Mojolicious::Plugin::GraphQL - a plugin for adding GraphQL route handlers
my $schema = GraphQL::Schema->from_doc(<<'EOF');
schema {
query: QueryRoot
}
type QueryRoot {
helloWorld: String
}
EOF
# for Mojolicious substitute "plugin" with $app->plugin(...
# Mojolicious::Lite (with endpoint under "/graphql")
plugin GraphQL => {
schema => $schema, root_value => { helloWorld => 'Hello, world!' }
};
# OR, equivalently:
plugin GraphQL => {schema => $schema, handler => sub {
my ($c, $body, $execute, $subscribe_fn) = @_;
# returns JSON-able Perl data
$execute->(
$schema,
$body->{query},
{ helloWorld => 'Hello, world!' }, # $root_value
$c->req->headers,
$body->{variables},
$body->{operationName},
undef, # $field_resolver
$subscribe_fn ? (undef, $subscribe_fn) : (), # only passed for subs
);
}};
# OR, with bespoke user-lookup and caching:
plugin GraphQL => {schema => $schema, handler => sub {
my ($c, $body, $execute, $subscribe_fn) = @_;
my $user = MyStuff::User->lookup($app->request->headers->header('X-Token'));
die "Invalid user\n" if !$user; # turned into GraphQL { errors => [ ... ] }
my $cached_result = MyStuff::RequestCache->lookup($user, $body->{query});
return $cached_result if $cached_result;
MyStuff::RequestCache->cache_and_return($execute->(
$schema,
$body->{query},
undef, # $root_value
$user, # per-request info
$body->{variables},
$body->{operationName},
undef, # $field_resolver
$subscribe_fn ? (undef, $subscribe_fn) : (), # only passed for subs
));
};
# With GraphiQL, on /graphql
plugin GraphQL => {schema => $schema, graphiql => 1};
plugin GraphQL => {
schema => $schema,
graphiql => {
enable => 1,
mode => 'esm', # esm | legacy_umd
graphiql_ver => '5.2.2',
react_ver => '19.1.0',
graphql_ver => '16.11.0',
},
};
This plugin allows you to easily define a route handler implementing a
GraphQL endpoint, including a websocket for subscriptions following
Apollo's subscriptions-transport-ws protocol.
As of version 0.09, it will supply the necessary promise_code
parameter to "execute" in GraphQL::Execution. This means your resolvers
can (and indeed should) return Promise objects to function
asynchronously. As of 0.15 these must be "Promises/A+" as subscriptions
require resolve and reject methods.
The route handler code will be compiled to behave like the following:
- Passes to the GraphQL execute, possibly via your supplied handler,
the given schema,
$root_valueand$field_resolver. Note as above that the wrapper used in this plugin will supply the hash-ref matching "PromiseCode" in GraphQL::Type::Library. - The action built matches POST / GET requests.
- Returns GraphQL results in JSON form.
Mojolicious::Plugin::GraphQL supports the following options.
Array-ref. First element is a classname-part, which will be prepended with
"GraphQL::Plugin::Convert::". The other values will be passed
to that class's "to_graphql" in GraphQL::Plugin::Convert method. The
returned hash-ref will be used to set options, particularly schema,
and probably at least one of resolver and root_value.
String. Defaults to /graphql.
A GraphQL::Schema object. As of 0.15, must be supplied.
An optional root value, passed to top-level resolvers.
An optional field resolver, replacing the GraphQL default.
An optional route-handler, replacing the plugin's default - see example above for possibilities.
It must return JSON-able Perl data in the GraphQL format, which is a hash
with at least one of a data key and/or an errors key.
If it throws an exception, that will be turned into a GraphQL-formatted error.
If being used for a subscription, it will be called with a fourth parameter as shown above. It is safe to not handle this if you are content with GraphQL's defaults.
Controls whether requesting the endpoint with Accept: text/html
will return the GraphiQL user interface. Defaults to false.
If the request includes a raw query parameter, the GraphiQL user
interface is skipped and the endpoint behaves as a normal GraphQL
endpoint even when Accept: text/html is sent.
May be set to a true value for the default behaviour, or a hash-ref for more control.
# Mojolicious::Lite
plugin GraphQL => {schema => $schema, graphiql => 1};
plugin GraphQL => {
schema => $schema,
graphiql => {
enable => 1,
mode => 'esm', # esm | legacy_umd
graphiql_ver => '5.2.2',
react_ver => '19.1.0',
graphql_ver => '16.11.0',
},
};
If the value is a hash-ref, the following keys are recognised:
-
enableBoolean, default true.
-
modeEither
esmorlegacy_umd. If omitted, defaults toesmfor non-subscription schemas andlegacy_umdfor subscription schemas. -
titlePage title. Defaults to
GraphiQL. -
graphiql_ver,react_ver,graphql_verPinned versions for the ESM GraphiQL UI.
-
graphiql_react_ver,graphiql_toolkit_verPinned versions for ESM helper packages.
-
legacy_graphiql_ver,legacy_react_ver,legacy_graphql_verPinned versions for the legacy UMD GraphiQL UI.
The ESM UI currently covers queries and mutations. Subscription support in the bundled GraphiQL UI remains on the legacy UMD path for now.
Defaults to 0, which means do not send. Otherwise will send a keep-alive packet over websocket every specified number of seconds.
Mojolicious::Plugin::GraphQL inherits all methods from Mojolicious::Plugin and implements the following new ones.
my $route = $plugin->register(Mojolicious->new, {schema => $schema});
Register renderer in Mojolicious application.
my $handler = make_code_closure($schema, $root_value, $field_resolver);
Returns the default handler closure used by the plugin when handler
is not provided.
The returned code reference executes the GraphQL operation against the given schema, using the supplied root value and optional field resolver.
This is mainly useful for advanced integrations where you want to reuse the plugin's default execution behavior but still wrap or compose it with your own logic.
Exportable is the function promise_code, which returns a hash-ref
suitable for passing as the 8th argument to "execute" in GraphQL::Execution.
To use subscriptions within your web app, just insert this JavaScript:
<script src="//unpkg.com/subscriptions-transport-ws@0.9.16/browser/client.js"></script>
# ...
const subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient(websocket_uri, {
reconnect: true
});
subscriptionsClient.request({
query: "subscription s($c: [String!]) {subscribe(channels: $c) {channel username dateTime message}}",
variables: { c: channel },
}).subscribe({
next(payload) {
var msg = payload.data.subscribe;
console.log(msg.username + ' said', msg.message);
},
error: console.error,
});
Note the use of parameterised queries, where you only need to change
the variables parameter. The above is adapted from the sample app,
https://github.com/graphql-perl/sample-mojolicious.
When GraphiQL is enabled, subscription schemas default to the bundled
legacy UMD UI for compatibility with Apollo's
subscriptions-transport-ws protocol.
https://github.com/apollographql/subscriptions-transport-ws#client-browser - Apollo documentation
Ed J
Based heavily on Mojolicious::Plugin::PODRenderer.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.