graphql-perl - graphql-js tutorial translation to graphql-perl and Mojolicious::Plugin::GraphQL
Version 0.16 of GraphQL implements most of using the Schema Definition Language to create schemas, rather than doing so programmatically. So let's now translate all the idioms in the JavaScript GraphQL tutorial into Perl!
EDIT version 0.17 implements the rest, so this tutorial translated has been updated slightly as you don't need to specify a schema
at all if you call your query type Query
, etc.
The format I'll use is to give each of the JS tutorial pages, with the Perl code to do what's given there:
Getting Started With GraphQL.js
You will need Perl 5.14. Consider using perlbrew to get this.
cpanm Mojolicious::Plugin::GraphQL # also gets Mojolicious and GraphQL
Then the first script:
use 5.014; use GraphQL::Schema; use GraphQL::Execution 'execute'; use Data::Dumper; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { helloWorld: String } EOF say Dumper execute($schema, "{ helloWorld }", { helloWorld=>"Hello world!" });
Running an Express GraphQL Server
We'll use Mojolicious::Lite. There's a sample applet on https://github.com/graphql-perl/sample-mojolicious.
use Mojolicious::Lite; use GraphQL::Schema; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { helloWorld: String } EOF plugin GraphQL => { schema => $schema, root_value => { helloWorld => 'Hello, world!' }, graphiql => 1, }; app->start;
Execute with:
./myapp.pl daemon -l http://*:5000
Look at: http://localhost:5000/graphql.
GraphQL Clients
(Note differences are only for ports and field name)
curl -X POST -H "Content-Type: application/json" \ -d '{"query": "{ helloWorld }"}' \ http://localhost:5000/graphql
JavaScript:
var xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.open("POST", "/graphql"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Accept", "application/json"); xhr.onload = function () { console.log('data returned:', xhr.response); } xhr.send(JSON.stringify({query: "{ helloWorld }"}));
Note that (I believe) since the tutorial was written, GraphiQL now has a "variables" pane so you can use it for that. The JS console snippet will also work.
Basic Types
use Mojolicious::Lite; use GraphQL::Schema; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { quoteOfTheDay: String random: Float! rollThreeDice: [Int] } EOF my $root_value = { quoteOfTheDay => sub {rand() < 0.5 ? 'Take it easy' : 'Salvation lies within'}, random => sub {rand()}, rollThreeDice => sub {[map 1+int(rand()*6), (1..3)]}, }; plugin GraphQL => { schema => $schema, root_value => $root_value, graphiql => 1, }; app->start;
Passing Arguments
use Mojolicious::Lite; use GraphQL::Schema; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { rollDice(numDice: Int!, numSides: Int): [Int] } EOF my $root_value = { rollDice => sub { my $args = shift; [map 1+int(rand()*$args->{numSides}), (1..$args->{numDice})]}, }; plugin GraphQL => { schema => $schema, root_value => $root_value, graphiql => 1, }; app->start;
Object Types
use Mojolicious::Lite; use GraphQL::Schema; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type RandomDie { numSides: Int! rollOnce: Int! roll(numRolls: Int!): [Int] } type Query { getDie(numSides: Int = 6): RandomDie } EOF { package RandomDie; sub new { my ($class, $sides) = @_; return bless {numSides=>$sides}, $class; } sub numSides { shift->{numSides} } sub rollOnce { my ($self) = @_; 1 + int(rand() * $self->{numSides}) } sub roll { my ($self, $args) = @_; [map $self->rollOnce, (1..$args->{numRolls})] } } my $root_value = { getDie => sub { my $args = shift; RandomDie->new($args->{numSides}) }, }; plugin GraphQL => { schema => $schema, root_value => $root_value, graphiql => 1, }; app->start;
Mutations and Input Types
use Mojolicious::Lite; use GraphQL::Schema; my $schema = GraphQL::Schema->from_doc(<<'EOF'); input MessageInput { content: String author: String } type Message { id: ID! content: String author: String } type Query { getMessage(id: ID!): Message } type Mutation { createMessage(input: MessageInput): Message updateMessage(id: ID!, input: MessageInput): Message } EOF { # If Message had any complex fields, we'd put them on this object. package Message; sub new { my ($class, $id, $args) = @_; return bless {id => $id, %{$args->{input}}}, $class; } sub id { shift->{id} } sub content { shift->{content} } sub author { shift->{author} } } my $fakedb = {}; my $root_value = { getMessage => sub { my $id = shift->{id}; die "No message $id\n" if !$fakedb->{$id}; Message->new($id, $fakedb->{$id}) }, createMessage => sub { my $input = shift; # Create a random id for our "database". my $id = int 1000 * rand(); $fakedb->{$id} = {%$input}; Message->new($id, $input) }, updateMessage => sub { my $args = shift; my ($id, $input) = @{$args}{qw(id input)}; die "No message $id\n" if !$fakedb->{$id}; # This replaces all old data, but some apps might want partial update. $fakedb->{$id} = {%$input}; Message->new($id, $input) }, }; plugin GraphQL => { schema => $schema, root_value => $root_value, graphiql => 1, }; app->start;
Authentication and Express Middleware
This is a large topic. In order to stay with the non-blocking Mojo idiom, you should use Mojolicious-compatible means of authentication. One method is using under
, as discussed here. There is also a Mojolicious plugin to make this easier.
Additionally, the Mojolicious GraphQL plugin will by default pass the request headers on as the "context" (per-request) object to GraphQL::Execution::execute, but with a route-handler you can do anything you like. The plugin's SYNOPSIS gives an example.
Constructing Types
Using Schema language:
use Mojolicious::Lite; use GraphQL::Schema; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type User { id: String name: String } type Query { user(id: String): User } EOF # Maps id to User hashref my $fakedb = { a => { id => 'a', name => 'alice' }, b => { id => 'b', name => 'bob' }, }; my $root_value = { user => sub { my $id = shift->{id}; die "No user $id\n" if !$fakedb->{$id}; $fakedb->{$id} }, }; plugin GraphQL => { schema => $schema, root_value => $root_value, graphiql => 1, }; app->start;
Without Schema language, i.e. instead using resolvers on fields:
use strict; use warnings; use Mojolicious::Lite; use GraphQL::Schema; use GraphQL::Type::Object; use GraphQL::Type::Scalar qw($String); # Maps id to User hashref my $fakedb = { a => { id => 'a', name => 'alice' }, b => { id => 'b', name => 'bob' }, }; my $UserType = GraphQL::Type::Object->new( name => 'User', fields => { id => { type => $String }, name => { type => $String }, }, ); my $schema = GraphQL::Schema->new(query => GraphQL::Type::Object->new( name => 'Query', fields => { user => { type => $UserType, args => { id => { type => $String } }, resolve => sub { my ($root_value, $args) = @_; my $id = $args->{id}; die "No user $id\n" if !$fakedb->{$id}; $fakedb->{$id} }, }, }, )); plugin GraphQL => { schema => $schema, graphiql => 1, }; app->start;
For more about resolvers in graphql-perl: graphql-perl resolver documentation.
express-graphql
For more on this, see the Mojolicious::Plugin::GraphQL documentation.
Acknowledgements
The porting of GraphQL to Perl 5 is sponsored by Perl Careers.
Thanks to the folks on #mojo on irc.perl.org for valuable pointers on improving this posting.
Leave a comment