A history of Symfony and GraphQL
A more durable url for this article is available on my personal blog
You might have heard of a new sheriff in town: GraphQL. And then wondered how you could use it with *insert your non-JS language here*.
TLDR;
If you are very lazy and just want a solution, here is my demo project, I’ll try to keep it updated: https://github.com/florentdestremau/graphql-example
What is GraphQL? Small reminder
Quick memo for those who don’t know what I’m talking about: GraphQL is a query language designed by Facebook to represent a modern way to communicated data between your front-end and back-end. For example if you wanted to query for the latest article from a blog, you’d write something like this as a front-end query
articles(limit: 10) {
id
title
excerpt
categories {
id
name
}
comments(limit: 3) {
id
author {
id
name
}
content
}
}
Did you see what happened here? A whole hierarchy of information was requested in a single request, and now we have all the information needed to display a whole bunch of stuff:
- article titles with their link
- the article categories with links too
- the first articles’ comments, with a click-able link to the user’s profile
- the content of the first 3 comments on the article.
So this is the very basic usage of GraphQL. But it’s only half of the implementation, you still need the back-end to handle the query interpretation and then return the actual data.
The back-end implementation in Symfony
If you are convinced by the simplicity and power of this type of query from the front-end (I sure am!), you probably wonder how it turns out in your back-end. So now is the time for me to announce that I am a Symfony developper and thus, while Facebook gave plenty of tools for NodeJS lovers, I had to rely on other libraries.
Meet youshido/graphql-bundle, the bundle that makes it all easy for you !
Note: there is an php port linked on the GraphQL website but at the time I was looking for a Symfony implementation, the youshido bundle was a better option for me, feel free to try the PHP library out !
The objective of this article is to explain how we use this bundle to easily set up you GraphQL API for a simple use: front-end widgets. The YoushidoGraphQLBundle
provides several classes to structure your GraphQL query tree.
Youshido\GraphQL\Schema\AbstractSchema
to set up your basic architectureYoushido\GraphQL\Type\Object\AbstractObjectType
to set up query-out formatting (in all of my use cases I used theAbstractObjectType
rather than theAbstractType
)Youshido\GraphQLBundle\Field\AbstractContainerAwareField
to set up any query-in arguments and actions
For example you will have a tree structure like this:
Schema
- Query
- ArticlesField
- CommentsField
- UsersField
- CategoriesField
- Mutations
- PostArticleField
And each of these Fields will respectively return ArticleType
, CommentType
, UserType
, CategoryType
, or more exactly aListType
of these.
The Fields
Here is what a simple ArticlesField
would look like.
class ArticlesField extends AbstractContainerAwareField
{
public function resolve($value, array $args, ResolveInfo $info)
{
return $this->container->get('doctrine.orm.entity_manager')->getRepository(Article::class)->findAll();
} public function getType()
{
return new ListType(new ArticleType());
}
}
Notice the 2 key functions here.
resolve
is the "controller", where you have access to the container and can call any service to retrieve the DATAgetType
is the "template", you express what type of return format you allow for the GraphQL schema to have. We’ll get to that further down.
There is an extra function that you can use, it’s the build
function:
public function build(FieldConfig $config)
{
$config->addArguments(
[
'published' => new BooleanType(),
]
);
}
This argument is exposed to the query, for instance you could query only articles that are published
articles(published: true) {
id
title
body
}
And then you can update your resolver:
public function resolve($value, array $args, ResolveInfo $info)
{
return $this->container
->get('doctrine.orm.entity_manager')
->getRepository(Article::class)
->findBy([
'published' => $args['published'],
]);
}
And there you go, filtered results !
The Types
So this was the “controller” part of the query, and now let’s get to the “templating” part. The Types (kind of a unfortunate naming, in my opinion, with the form Types as well, but hey I didn’t write it ¯\_(ツ)_/¯) are the classes that defines the JSON output for the file. Here is an example of what the ArticleType
would be.
class ArticleType extends AbstractObjectType
{
public function build($config)
{
$config->addFields(
[
'id' => new IdType(),
'title' => new StringType(),
'body' => new StringType(),
'categories' => new ListType(new CategoryType()),
]
);
}
}
This way you define what fields are available to retrieve. Now this is a simple version, but you can add custom fields that require a bit more of an assistance. For instance to display the phone number of a user I would need to use the container:
As you can see the ResolveInfo
entity has access to the container so you can use different services to render or simply do some logic stuff.
And there you go, a full GraphQL API!
Oh and one last thing, did I tell you that you had a live explorer at the /graphql/explorer
?
I actually made an API boilerplate with the bundle already installed:
Liked what you read ? Hit the 👏 button, I will write some more!