Recently I have been working on something very amazing for API development, everyone has been using REST to access API, but now things are changing, in the front-end heavy world where things are consistently growing and new features added every week a fixed REST API is not very good option anymore, so what we need then? the answer is GraphQL. Let’s dive into details of this amazing query language and build a Twitter like app API in Laravel using GraphQL.
What is GraphQL?
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data. It was created by Facebook to fulfill growing needs of API consumption and it has been implemented in major languages.
Types
At the heart of any GraphQL implementation is a description of what types of objects it can return, described in a GraphQL type system and returned in the GraphQL Schema. For example here is a User type which we will use later in this post.
type User {
id: Integer
name: String,
email: String,
password: String,
avatar: String
}
Query
GraphQL queries declaratively describe what data the issuer wishes to fetch from whoever is fulfilling the GraphQL query. For example, if you want to get User with id
and name
you can query it like this:
query UserNameAndIDQuery {
users {
id,
name
}
}
which will give us only the name and id of users like this:
{
"data":{
"users":[
{
"id":1,
"name":"Garnet Little"
},
{
"id":2,
"name":"Lisa Moen MD"
}
]
}
}
Mutation
Most discussions of GraphQL focus on data fetching, but any complete data platform needs a way to modify server-side data as well, mutation can do that. These are set of queries which can perform create, update & delete on the database.
mutation {
createUser(
name: "Saqueib Ansrai",
email: "hello@example.com",
password:"secret"
)
{ id,
email,
name
}
}
Above mutation will create a User and return back the id, email & name.
{
"data" : {
"createUser" : {
"id": 1,
"email": "hello@example.com",
"name": "Saqueib Ansrai"
}
}
}
Now let’s see how we can use this in a Laravel App, I am going to use Laravel 5.4 and to implement server of GraphQL I am going to use laravel-graphql package. This PHP implementation is a thin wrapper around your existing data layer and business logic. It doesn’t dictate how these layers are implemented or which storage engines are used. Instead, it provides tools for creating API for your existing app.
Setup Laravel
Create a new Laravel 5.4 App and once installed configure a database and install laravel-graphql package using composer.
composer require folklore/graphql
Once that’s installed, add service provider and alias for the facade.
'providers' => [
...
Folklore\GraphQL\ServiceProvider::class,
]
....
'aliases' => [
....
'GraphQL' => Folklore\GraphQL\Support\Facades\GraphQL::class,
]
Now let’s publish our GraphQL config file
php artisan vendor:publish --provider="Folklore\GraphQL\ServiceProvider"
That completes installation and everything is setup. Let’s move on the Model and Migration.
Create models
We will be building a tweeter like an app DB schema, let’s add an avatar field on provided User model and create Profile, Tweet, Reply & Like model with migration.
php artisan make:model Profile -m
php artisan make:model Tweet -m
php artisan make:model Reply -m
php artisan make:model Like -m
Now let’s fill up the migration schema in all stubs.
Schema::create('profiles', function (Blueprint $table) {
$table->increments('id');
$table->string('bio')->nullable();
$table->string('designation')->nullable();
$table->string('company')->nullable();
$table->string('city', 70)->index()->nullable();
$table->string('country', 100)->nullable();
$table->date('dob')->nullable();
$table->unsignedInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->timestamps();
});
Schema::create('tweets', function (Blueprint $table) {
$table->increments('id');
$table->string('body');
$table->unsignedInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->timestamps();
});
Schema::create('replies', function (Blueprint $table) {
$table->increments('id');
$table->string('body');
$table->unsignedInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->unsignedInteger('tweet_id');
$table->foreign('tweet_id')
->references('id')
->on('tweets')
->onDelete('cascade');
$table->timestamps();
});
Schema::create('likes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->unsignedInteger('tweet_id');
$table->foreign('tweet_id')
->references('id')
->on('tweets')
->onDelete('cascade');
$table->unique(['user_id', 'tweet_id']);
$table->timestamps();
});
I have gone ahead and created Model factory and Database seeder for this. Run the php artisan db:seed
to seed dummy data.
Now laravel stuff out of the way lets now start defining the Types for User to use with GraphQL.
User Type
Let’s define the UserType, we need to create our class in app/GraphQL/Type/UserType.php
namespace App\GraphQL\Type;
use GraphQL;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType {
protected $attributes = [
'name' => 'User',
'description' => 'A user'
];
/*
* Uncomment following line to make the type input object.
* http://graphql.org/learn/schema/#input-types
*/
// protected $inputObject = true;
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the user'
],
'name' => [
'type' => Type::string(),
'description' => 'The name of user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
'avatar' => [
'type' => Type::string(),
'description' => 'The avatar of user'
],
'created_at' => [
'type' => Type::string(),
'description' => 'Creation datetime'
],
'updated_at' => [
'type' => Type::string(),
'description' => 'Updating datetime'
],
// Nested Resource
'tweets' => [
'args' => [
'id' => [
'type' => Type::int(),
'description' => 'id of the tweet',
],
'first' => [
'type' => Type::int(),
'description' => 'limit result',
],
],
'type' => Type::listOf(GraphQL::type('Tweet')),
'description' => 'tweet description',
],
'profile' => [
'type' => GraphQL::type('Profile'),
'description' => 'user profile',
]
];
}
// If you want to resolve the field yourself, you can declare a method
// with the following format resolve[FIELD_NAME]Field()
protected function resolveEmailField($root, $args)
{
return strtolower($root->email);
}
protected function resolveCreatedAtField($root, $args)
{
return (string) $root->created_at;
}
// You can also resolve any nested resource filed in same way
protected function resolveTweetsField($root, $args)
{
$tweets = $root->tweets();
if (isset($args['id'])) {
return $tweets->where('id', $args['id']);
}
// check for limit
if( isset($args['first']) ) {
$tweets = $tweets->limit($args['first'])->latest();
}
return $tweets->get();
}
protected function resolveProfileField($root, $args) {
return $root->profile;
}
}
Let me explain whats going on, GraphQL need 2 things to return data for a field, first is a fields
and second is a resolver
function which will get the data from the database for that field. In above class fields()
method we are returning a fields schema for user, only fields added here will be returned by GraphQL API.
And below fields() we have resolver overrides, for example I have used resolveEmailField()
method to format the email to make it lowercase. It’s like Accessor in Eloquent model, and after this we have some nested resources tweets and profile which are resolved by resolveTweetsField()
method and resolveProfileField()
respectively.
In Tweets resolver, we have some argument checking, which will be passed from the query and we return the has many relation tweets data from $root which is User model instance here.
Once you defined all the type you need to add them in config/graphql.php
files under types
array, thats how GraphQL will know about available types.
'types' => [
'User' => App\GraphQL\Type\UserType::class,
'Profile' => App\GraphQL\Type\ProfileType::class,
'Tweet' => App\GraphQL\Type\TweetType::class,
'Reply' => App\GraphQL\Type\ReplyType::class,
'Like' => App\GraphQL\Type\LikeType::class
],
Cool, now we have our Types, let’s define our Queries so we can access data through API call using GraphQL.
User Query
We want to create a query which will be for only reading data. Let’s define one to access Users, create a class under below namespace.
namespace App\GraphQL\Query;
use GraphQL;
use App\User;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Query;
class UsersQuery extends Query {
protected $attributes = [
'name' => 'users'
];
public function type()
{
return Type::listOf(GraphQL::type('User'));
}
public function args()
{
return [
'id' => ['name' => 'id', 'type' => Type::int()],
'email' => ['name' => 'email', 'type' => Type::string()],
'first' => ['name' => 'first', 'type' => Type::int()],
];
}
public function resolve($root, $args)
{
$user = new User;
// check for limit
if( isset($args['first']) ) {
$user = $user->limit($args['first'])->latest('id');
}
if(isset($args['id']))
{
$user = $user->where('id' , $args['id']);
}
if(isset($args['email']))
{
$user = $user->where('email', $args['email']);
}
return $user->get();
}
}
For sake of simplicity I have added all the resolver data fetching logic in the method, you should consider a service class to delegate fetching of data so later you can add caching etc.
This looks very similar to UserType class but it will be use to fetch the actual data, so first, we define the Type we are going to return in type()
method, in this case it will be Type::listOf(GraphQL::type('User'))
. Next we define all of the argument this query can take in args()
method. In here we have id
which we can use to filter user by id, email
for same and first
which will be used to limit the result by number.
Once you defined your Query, let’s once again add it in config/graphql.php
under schemas default queries array.
'schemas' => [
'default' => [
'query' => [
'users' => App\GraphQL\Query\UsersQuery::class
...
],
]
]
GraphQL users query
Now we can open a browser and get the user data. Let’s get all the users with id
and name
, by default you can access on http://localhost:8000/graphql
route, you can change this in graphql.php
config file.
I am running on localhost:8000, you need to match your dev app url to test it.
Here is how our query looks like:
query getUserNameAndId {
users{
id,
name
}
}
and a request URL will be like this
http://localhost:8000/graphql?query=query+getUserNameAndId{users{id,name}}
If you did everything right you will get the list of users, if something is wrong you will get an error with a message.
Limit Result
If you remember in query definition, we had some arguments, let’s use them. First let’s limit the result to only 5 users, passing first:5 will do the trick.
query getUserNameAndId {
users(first: 5){
id,
name
}
}
Request URL
http://localhost:8000/graphql?query=query+getUserNameAndId{users(first:5){id,name}}
Find user by ID or Email
You can also find a specific user by id
or email
since we have defined arguments in our UsersQuery.
query getUserById {
users(id:2){
id,
name
}
}
query getUserByEmail {
users(email:"saqueib@example.com"){
id,
name
}
}
Requests URL
http://localhost:8000/graphql?query=query+getUserById{users(id:5){id,name}}
http://localhost:8000/graphql?query=query+getUserByEmailId{users(email:"saqueib@example.com"){id,name}}
Access Nested Resource
Accessing the nested resource where GraphQL shines, in UserType we have defined tweets
and profile
as nested resource. Let see how we can get them in a query.
query getUserWithProfile {
users(first: 3){
id,
name,
profile {
bio,
city,
country
}
}
}
Request URL
http://localhost:8000/graphql?query=query+getUserWithProfile{users(first:3){id,name,profile{bio,city,country}}}
Similarly, you can access tweets of a user, let’s access first 3 tweets for a user.
query getUserWithTweets {
users(first: 3){
id,
name,
tweets(first: 3) {
body,
created_at
}
}
}
Request URL
http://localhost:8000/graphql?query=query+getUserWithTweets{users(first:3){id,name,tweets(first:3){body,created_at}}}
Power of GraphQL queries
As you can see you have lots of flexibility in terms of what exactly you need, if you do the same thing in REST API you probably need 3 different HTTP request to get all the things you needed or create some sort of alias endpoint on API to get userWithTweet
and another for userWithProfile
. It will make more difficult to change front-end of app since data need has changed, now you probably need another API endpoint with userProfileAndTweets
. As you can see it can get very messy very quickly. But with GraphQL you don’t need any other endpoint, it will be most likely will be a single endpoint and queries declaratively describe what data the issuer wishes to fetch.
Now we have a solid understanding how to fetch data, but what about making a change in data or persist new record.
Mutation in GraphQL
Let’s define the mutation to createUser
, updateUser
and deleteUser
.
Let’s create a file in app/GraphQL/Mutation/CreateUserMutation.php
and add below code.
namespace App\GraphQL\Mutation;
use GraphQL;
use App\User;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Mutation;
class CreateUserMutation extends Mutation {
protected $attributes = [
'name' => 'createUser'
];
public function type()
{
return GraphQL::type('User');
}
public function rules()
{
return [
'email' => 'required|email|unique:users',
'name' => 'required|min:2',
'password' => 'required|min:6',
'avatar' => 'url'
];
}
public function args()
{
return [
'name' => ['name' => 'name', 'type' => Type::string()],
'email' => ['name' => 'email', 'type' => Type::string()],
'password' => ['name' => 'password', 'type' => Type::string()],
'avatar' => ['name' => 'avatar', 'type' => Type::string()],
];
}
public function resolve($root, $args)
{
return User::create($args);
}
}
Mutation needs to mutate the data so it has one more rules()
method to validate the data, you can use laravel validation with a resolve function that does the data manipulation in the database. In this case, we create a user if data is valid. Let’s see it in action.
As we did for queries, let’s add the mutation in config/graphql.php
file under schemas default mutations array.
'schemas' => [
'default' => [
'query' => [
'users' => App\GraphQL\Query\UsersQuery::class
...
],
'mutation' => [
'createUser' => App\GraphQL\Mutation\CreateUserMutation::class,
'updateUser' => App\GraphQL\Mutation\UpdateUserMutation::class,
'deleteUser' => App\GraphQL\Mutation\DeleteUserMutation::class
]
]
]
UserCreate Mutation
To create a user we need to send the user data, its looks like query but this time we have to pass key value pairs, unlike in query you just ask for fields.
mutation {
createUser(
name:"Saqueib Ansari",
email:"hello@example.com",
password:"secret"
)
{
id,
email,
name
}
}
In this createUser mutation we create the user and in next block, we are asking to return the created users id, email & name.
Request URL
http://localhost:8000/graphql?query=mutation+users{createUser(name:"Saqueib Ansrai",email:"hello@example.com",password:"secret"){id,email,name}}
UserUpdate Mutation
Now let’s update a User using,UpdateUserMutation
here is the resolve function of this.
public function resolve($root, $args)
{
$user = User::find($args['id']);
if(! $user)
{
return null;
}
// update user
$fields = isset($args['password'])
? array_merge($args, ['password' => bcrypt($args['password'])])
: $args;
$user->update($fields);
return $user;
}
As you can see we asked id
argument from mutation query and trying to find a user with that, later we update it.
mutation updateUsersEmail{
updateUser(
id:2,
email:"email@example.com"
)
{
id,
email,
name
}
}
In this mutation, we are updating user email with id 2.
Request URL
http://localhost:8000/graphql?query=mutation+updateUsersEmail{updateUser(id:2,email:"email@example.com"){id,email,name}}
UserDelete Mutation
In delete mutation, if you just pass id and you can delete a user.
public function resolve($root, $args)
{
if( $user = User::findOrFail($args['id']) ) {
$user->delete();
return $user;
}
return null;
}
And now query will be like this:
mutation deleteUserById{
deleteUser(
id:2
)
{
id,
email,
name
}
}
This call will delete the user with id 2 and return the deleted user id, name & email.
Request URL
http://localhost:8000/graphql?query=mutation+deleteUserById{deleteUser(id:2){id,email,name}}
With that we have a complete CRUD operation in GraphQL, I will create Follow and Tweet queries and mutation and it will be available in GitHub repo.
Conclusion
Although GraphQL is very cool and defiantly going to replace REST in future but at the time writing of this post GraphQL is still a specification and not very well implemented in laravel community, the package we have used laravel-graphql is still a work in progress as of 15th Aug 2017, but I think the amount of flexibility GraphQL is providing its worth the time to learn it. I have not covered all the code for brevity but you can access it at GitHub. I would love to hear from you. In next part, I will be building a twitter like app in Vue.js with all the bells and whistles possible using this GraphQL API soon. As always if you like it please comment I love to hear your thoughts and feedback on it.
Wow, this is a game changer. Thanks!
What I’d really like to see is a post on high performance graphql in Laravel. Ya know with eager loading of relations based on the ResolveInfo data. Ideally covering each relation type off both a root and nested node.
Realistically this is the only thing stopping us from taking up graphql given that we often build a page and find it’s issuing 600+ queries – implementing eager loading usually takes it down to about 7 and can cut seconds off the load time
Totally agree with this, in next part i will try to optimize queries and build a SPA for frontent in vue.js
thanks , now i am able to work it out
i get method not allowed exception error
i am using this link http://localhost/kripro/jsemantic/graphql?query=query+getUserNameAndId{users{id,name}}
please run php artisan route:list to check if graphql route is present and pointing to right controller method
| | GET|HEAD | graphiql/{graphql_schema?} | graphql.graphiql | FolkloreGraphQLGraphQLController@graphiql | |
| | GET|HEAD | graphql/{graphql_schema?} | graphql.query | FolkloreGraphQLGraphQLController@query | graphql,auth |
| | POST | graphql/{graphql_schema?} | graphql.query.post | FolkloreGraphQLGraphQLController@query | graphql,auth |
no its not there what should i do to get that in the list
Please add below in config/app.php
'providers' => [
...
FolkloreGraphQLServiceProvider::class,
]
and the publish the config by running
php artisan vendor:publish --provider="FolkloreGraphQLServiceProvider"
you will see config/graphql.php‘controllers’ => FolkloreGraphQLGraphQLController::class.’@query’,
which register above routes.
i get method not allowed exception
http://localhost/kripro/jsemantic/graphql?query=query+getUserNameAndId{users{id,name}}
but chromeIql working fine
https://uploads.disquscdn.com/images/ea850124a67af228210da486d39afdedd85efb44bac07f55d99edafddad89cdd.png
thanks a lot ,it helped me , just a small query how i can have this.
{
“status”:200,
“message”:”Success”,
“data”: {
“deleteUser”: {
“id”: 15
}
}
}
Wow !
how i can resolve the fields in mutation,
i mean suppose i have a field by name ‘firstName’ but i want to save it in database table as column name first_name, so i need to resolve this in mutation.pls help
I think it’s cooler when this Laravel package add artisan command to create GraphQL Type or Query etc.
how to add auth middleware to the graph route ?