Creating a GraphQL Server With PHP

by Mauro Chojrin

10 min read

What is GraphQL?

GraphQL is, at its core, a query language designed for optimal access to information accessible through APIs.

The motivation behind GraphQL is to provide an easy, scalable, and future-proof way for FrontEnd applications to interact with their BackEnd counterparts.

What’s Wrong With RESTful

In a way, GraphQL was born as a reaction to RESTful’s shortcomings. Let’s examine the main ones.

An All-Or-Nothing Approach to Data Retrieval

The whole premise of REST is to exchange resources, which are considered indivisible units of information.

In many cases though, when asking for a particular resource, the requestor is not interested in each and every piece of data related to it but only a subset of it.

For instance, if you are looking for information about a user in your system, you’d issue a request to an endpoint such as https://yourdomain.com/api/v1/user/aa32fd to get back something like:

{
	"id": "aa32fd",
	"name": "Mauro Chojrin",
	"email": "[email protected]",
	"birthday": "1977-12-22",
	"country": "https://yourdomain.com/api/v1/country/1ae29d",
	"gender": "Male"
}

Nothing wrong with that but… what if all you needed was the name of the user?

I know, I know, it’s just a few extra bytes in a string, right? Unless of course, the actual URL was something more like https://yourdomain.com/api/v1/user.

In that case, the few extra bytes become a whole lot of extra bytes!

One Query Per Object

In the above example, if you wanted to know which country I live in you’d have to issue a second request.

That’s not necessarily a problem but it certainly can make the application rather inefficient in certain scenarios for many PHP developers out there..

Schema Rigidity

What if you needed to stop supporting the storage of the gender field all of a sudden? Once your API has been implemented by many different clients there’d be no escape to changing your endpoints from https://yourdomain.com/api/v1 to https://yourdomain.com/api/v2 with all the implied hassle and associated costs of keeping two versions alive at the same time.

Loose Semantics

This is probably the point where REST and GraphQL differ the most.

In REST all the information exchanged is text, there’s no type checking built-in. GraphQL, in turn, is strongly typed.

Also, If you need to allow clients to do some filtering or pagination on the data they want, you need to resort to hacks such as extraneous custom HTTP headers.

GraphQL has these capabilities as part of its core. 

In summary, REST is a lower-level protocol than GraphQL.

Quick Introduction to GraphQL

A GraphQL application, unlike a RESTful one, exposes a single endpoint, which mainly consists of the query parser and response generator.

Probably the easiest way to think about a GraphQL API is as if it were a database server where there is a dedicated process listening to a specific port through which every possible query comes and, internally, there’s a component responsible for interpreting what is being requested.

A GraphQL API is built on top of a basic definition, the schema. This definition will serve two purposes:

  1. Allow the query parser to validate the requests and produce the desired output.
  2. Act as a specification for the client to know what can be obtained from the API and how to query it.

The main concepts you need to have clarity about in order to create a valid GraphQL schema are:

  • Type
  • Query
  • Resolvers

Let’s look at them one by one.

Type

At the very top of GraphQL specification, you find the Types. Types are definitions similar to classes in the sense that they contain fields, which are also instances of other (simpler) Types.

There are a few basic types available, called Scalar Types, such as:

  • String
  • Int
  • Id

And then, you can build your own Types. In fact, you must define your own types as part of your schema.

The schema is a formal definition of the types of objects that are exposed via the GrahpQL service.

GraphQL has its own type language that’s used the write GraphQL schemas: The Schema Definition Language (SDL). This language has a similar syntax to json. In our example it would look like this:

type Country {
id: String!
name: String!
code: String!
}

type User {
id: String!
name: String!
email: String
birthdate: Date
country: Country!
gender: String
}

There’s a whole lot more nuances about the Types and schemas. Check out this article for more information.

A GraphQL schema isn’t complete until you define, at least, one root type. Like this:

type Query {
User(id: ID!): User
}

This definition states that a client can query the API for a user, providing the id of the desired object and, in return, they’ll get a set of fields corresponding to the definition of the User Type… unless they specify something other, as you’ll see in the following examples.

Query

When the best GraphQL server receives a request from a client, the text is matched against the Query definition. If everything checks out, the output is produced according to the specified information the client wants to get back.

The GraphQL query language is basically about selecting fields on objects.

Queries are expressed in a language somewhat similar to JSON. The idea is for it to be easy to understand what you’ll be expecting to get back just by reading the query itself.

For instance, if I wanted to get the name of the user with id aa32fd, I’d post the following to the server:

{
User(id: "aa32fd") {
name
}
}

And I’d get back:

{
"data": {
"User": {
"name": "Mauro"
}
}
}

If I wanted to get back the name of the user and their country of residence, the query would look like this:

{
User {
name
country {
name
}
}
}

This would in turn produce the following result:

{
	"data": {
	"User": {
		"name": "Mauro",
		"country": {
			"name": "Spain"
		}
	}
}
}

Again, there’s a lot more to know about GraphQL queries. If you’re interested, here’s a good resource for you.

How to Create a GraphQL Server With PHP

As you can see, in order to expose a GraphQL API you need a fairly complex parser. Of course, there’s nothing stopping you from putting one together from scratch but, why would you?

By now you’re probably thinking there must be nice open-source libraries to deal with all this hassle, right? Yes, there are. In fact, there are many of them.

In this post, I’ll be discussing GraphQL-PHP for it is one of the most popular.

Creating the Project

Let’s start by creating a new directory for the project:

mkdir gql-test && cd gql-test 

Then, let’s initialize it as a composer project:

composer init

Fill the project details with the default values, and answer yes when asked. 

Would you like to define your dependencies (require) interactively [yes]?

And then search for the package webonyx/graphql-php.

Keep accepting the defaults until you see a question such as:

Add PSR-4 autoload mapping? Maps namespace "Mauro\GqlTest" to the entered relative path. [src/, n to skip]

Answer n.

If everything went well you should see something like:

{
    "name": "mauro/gql-test",
    "require": {
        "webonyx/graphql-php": "^14.11"
    },
    "authors": [
        {
            "name": "Mauro Chojrin",
            "email": "[email protected]"
        }
    ]
}

Do you confirm generation [yes]?

Answer yes to this question and the following and you’ll be good to go.

Bringing the Library In 

Create a new file inside gql-test called graphql.php containing the following:

<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

Probably the easiest way to make use of GraphQL-PHP is to rely on the StandardServer class.

In order to do this you need to add a few elements to your file: 

  1. A use sentence
  2. The creation of a StandardServer object
  3. The invocation to the handleRequest method

Something like this:

<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

use GraphQL\Server\StandardServer;

$server = new StandardServer();

$server->handleRequest();

The problem is, that the constructor for StandardServer expects a parameter: the schema definition.

So, you’ll need to do a couple of things:

  1. Another use statement
  2. The creation of an instance of Schema

And, in order to create a Schema you’re going to need, at least, a definition for its QueryType.

In summary, just to get things off the ground, you’ll need a code similar to:

<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

use GraphQL\Server\StandardServer;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;

$queryType = new ObjectType([
    'name' => 'Query',
    'fields' => [
        'echo' => [
            'type' => Type::string(),
            'resolve' => fn ($rootValue, array $args): string => 'Hello world!',
        ],
    ],
]);

$schema = new Schema(
    [
        'query' => $queryType,
    ]
);

$server = new StandardServer([
    'schema' => $schema,
]);

$server->handleRequest();

Here you can see how a schema is built around a query that can be used by posting:

{
  echo
}

To the server.

If you want to see it running, just start your server using:

php -S localhost:8000 graphql.php

And then, issue a request like this:

curl 'http://localhost:8000' -H 'Content-Type: application/json' -H 'Accept: application/json' --data-binary '{"query":"{echo}"}'

To get:

{"data":{"echo":"Hello world!"}}

Back from the server.

Now, let’s review what we have here in the query definition:

new ObjectType([
    'name' => 'Query',
    'fields' => [
        'echo' => [
            'type' => Type::string(),
            'resolve' => fn ($rootValue, array $args): string => 'Hello world!',
        ],
    ],
])

Here, we’re creating an instance of ObjectType named Query which exposes a field named echo (meaning the client can ask for this field to be present within the response).

What’s interesting about this field is its resolve property. Here’s where the magic happens. 

As you can see, this property is supposed to be defined as a callable object receiving two arguments: property $rootValue and $args and returning a value compatible with the type defined in the type property. How those inputs are mapped to the output is completely up to you: you can fetch data from storage and perform as many transformations as you see fit.

This clearly is an extremely simple example and it doesn’t show you the extensive power of GraphQL but it serves the purpose of introducing you to this great library. If you want to see a more complex example, including docker configuration, take a look at this repository.

Conclusion

I hope this post helped you see beyond RESTful and picked your curiosity about GraphQL. 
If you really want to take things to the next level, I recommend you take a look at api-platform.

If you, as a PHP developer are interested to find out more about PHP topics, check out how to install php in sublime text 3 or learn about the difference between PHP and Javascript.

FAQs

Q: What is a GraphQL server?
A GraphQL server is a server-side implementation of the GraphQL spec. In other words, a GraphQL server exposes your data as a GraphQL API that your client applications can query for data. These clients could be a single page application, a CMS like Drupal, a mobile app, or anything else.
Q: What is GraphQL?
GraphQL is, at its core, a query language designed for optimal access to information accessible through APIs.
Q: How to Create a GraphQL Server With PHP?
There are several steps:
  • Create the project
  • Initialize it as a composer project
  • Fill the project details with the default values, and answer yes when asked
  • And then search for the package webonyx/graphql-php.
  • Keep accepting the defaults
  • Then bring the library in
Mauro Chojrin
Mauro Chojrin
Senior PHP Engineer

Mauro is a PHP trainer and consultant. He has been involved in the IT Industry since 1997 in a wide array of positions, including technical support, development, team leadership, IT management, and teaching. He also maintains his blog and YouTube channel where he shares his knowledge with the world.

Expertise
  • PHP
  • PHP
  • Laravel
  • MySQL

Ready to start?

Get in touch or schedule a call.