GraphQL - Laravel

A graphql egy alternatíva a rest api-k mellett. Graphql-ben egyetlen végpont van és egy speciális lekérdező nyelvvel tudunk adatokat lekérdezni vagy módosítani. Ebben a példában, laravel környezetben fogunk graphql-el dolgozni. Egy backend oldali megvalósítás fog készülni.

Két részre bontjuk a graphql "parancsokat". Vannak a query-k és a mutációk. Előbbiekkel adatokat kérdezünk le, utóbbiakkal új adatot szúrunk be, módosítunk törlünk. Minden esetben egy json-t ad vissza válaszként.

Tegyük fel a következő csomagokat:
composer require mll-lab/laravel-graphql-playground

composer require rebing/graphql-laravel


Az application/config/graphql.php-ban vegyük fel a mutációkat és a query-ket.
 
    'schemas' => [
        'default' => [
            'query' => [
                App\GraphQL\Queries\UsersQuery::class,
                App\GraphQL\Queries\UserQuery::class,
            ],
            'mutation' => [
                'newUser' => App\GraphQL\Mutations\CreateUserMutation::class,
                'updateUser' => App\GraphQL\Mutations\UpdateUserMutation::class,
                'deleteUser' => App\GraphQL\Mutations\DeleteUserMutation::class,
            ],
            'types' => [
                App\GraphQL\Types\UserType::class,
            ],
            'middleware' => [],
            'method' => ['get', 'post'],
        ],
    ],
    'types' => [
        App\GraphQL\Types\UserType::class,
    ],

Vegyük fel a típust application/app/GraphQL/types/UserType.php
 
<?php

namespace App\GraphQL\Types;

use App\Models\User;
use App\GraphQL\Fields\FormattableDate;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
use Rebing\GraphQL\Support\Facades\GraphQL;

/**
 * Class UserType
 * @package App\GraphQL\Types
 */
class UserType extends GraphQLType
{
    /**
     * @var string[]
     */
    protected $attributes = [
        'name' => 'User',
        'description' => 'User',
        'model' => User::class,
    ];

    /**
     * @return array
     */
    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::int(),
                'description' => 'The id of the user',
            ],
            'email' => [
                'type' => Type::string(),
                'description' => 'The email of user',
                'resolve' => function ($root, $args) {
                    return strtolower($root->email);
                },
                'rules' => ['required', 'email']
            ],
            'name' => [
                'type' => Type::string(),
                'description' => 'The name of user',
            ],
            'dateOfBirth' => [
                'type' => Type::string(),
                'description' => 'The dateofbirth of user'
            ],
            'isActive' => [
                'type' => Type::boolean(),
                'description' => 'True, if the queried user is active',
            ],
            'createdAt' => new FormattableDate([
                'alias' => 'created_at',
            ]),
            'updatedAt' => new FormattableDate([
                'alias' => 'updated_at',
            ]),
            'phones' => [
                'type' => Type::listOf(GraphQL::type('UserPhones')),
                'description' => 'user phones',
            ]
        ];
    }
}

Vegyünk fel query-ket application/app/GraphQL/Queries/UsersQuery.php
 
<?php

namespace App\GraphQL\Queries;

use App\GraphQL\Services\UserService;
use App\GraphQL\Types\TagInput;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use App\GraphQL\Middleware;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Closure;

/**
 * Class UsersQuery
 * @package App\GraphQL\Queries
 */
class UsersQuery extends Query
{
    /**
     * @var UserService
     */
    private UserService $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    /**
     * @var string[]
     */
    protected $middleware = [
        Middleware\ResolvePage::class,
    ];

    /**
     * @var string[]
     */
    protected $attributes = [
        'name' => 'users',
        'description' => 'List of users with phones '
    ];

    /**
     * @return Type
     */
    public function type(): Type
    {
        return GraphQL::paginate('User');
    }

    /**
     * @return array[]
     */
    public function args(): array
    {
        return [
            'limit' => [
                'name' => 'limit',
                'type' => Type::int(),
                'rules' => ['required'],
                'description' => 'Limit of number',
            ],
            'page' => [
                'name' => 'page',
                'type' => Type::int(),
                'rules' => ['required'],
                'description' => 'Page of number',
            ],
            'sortBy' => [
                'name' => 'sortBy',
                'type' => Type::string(),
                'rules' => ['required'],
                'description' => 'Sort by of list params: ("id","name","email","dateOfBirth","isActive")',
            ],
            'order' => [
                'name' => 'order',
                'type' => Type::string(),
                'rules' => ['required'],
                'description' => 'Sort of list params: ("asc","desc")',
            ],
            'phones' => [
                'type' => Type::listOf(new TagInput('UserPhones!')),
                'description' => 'User phones',
            ]
        ];
    }

    /**
     * @param $root
     * @param $args
     * @param $context
     * @param ResolveInfo $resolveInfo
     * @param Closure $getSelectFields
     * @return LengthAwarePaginator
     */
    public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields): LengthAwarePaginator
    {
        return $this->userService->getAllPaginated($getSelectFields(), $args);
    }
}

Vegyünk fel mutációt user létrehozáshoz application/app/GraphQL/Mutation/CreateUserMutation.php
 
<?php

namespace App\GraphQL\Mutations;

use App\Models\User;
use App\GraphQL\Services\UserService;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;

/**
 * Class NewUserMutation
 * @package App\GraphQL\Mutations
 */
class CreateUserMutation extends Mutation
{
    /**
     * @var string[]
     */
    protected $attributes = [
        'name' => 'newUser',
        'description' => 'Create a new user with phone'
    ];
    /**
     * @var UserService
     */
    private UserService $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    /**
     * @return Type
     */
    public function type(): Type
    {
        return GraphQL::type('User');
    }

    /**
     * @return array[]
     */
    public function args(): array
    {
        return [
            'name' => [
                'name' => 'name',
                'type' => Type::nonNull(Type::string()),
                'rules' => ['required'],
                'description' => 'User name'
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::nonNull(Type::string()),
                'rules' => ['required', 'email'],
                'description' => 'User email'
            ],
            'dateOfBirth' => [
                'name' => 'dateOfBirth',
                'type' => Type::nonNull(Type::string()),
                'rules' => ['required'],
                'description' => 'User date of birth'
            ],
            'isActive' => [
                'name' => 'isActive',
                'type' => Type::nonNull(Type::boolean()),
                'rules' => ['required'],
                'description' => 'Active user'
            ],
            'phoneNumber' => [
                'name' => 'phoneNumber',
                'type' => Type::nonNull(Type::string()),
                'rules' => ['required', 'regex:/^([0-9\s\/-]{8,13})$/', 'min:7', 'max:12'],
                'description' => 'User phone number'
            ],
        ];
    }

    /**
     * @param $rootValue
     * @param array $args
     * @return User
     */
    public function resolve($rootValue, array $args): User
    {
        return $this->userService->create($args);
    }
}

Vegyünk fel mutációt user módosításhoz application/app/GraphQL/Mutation/UpdateUserMutation.php
 
<?php
namespace App\GraphQL\Mutations;
use App\GraphQL\Services\UserService;
use Closure;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
use App\Models\User;

/**
 * Class UpdateUserMutation
 * @package App\GraphQL\Mutations
 */
class UpdateUserMutation extends Mutation
{
    /**
     * @var string[]
     */
    protected $attributes = [
        'name' => 'updateUser',
        'description' => 'Update user data'
    ];

    /**
     * @var UserService
     */
    private UserService $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    /**
     * @return Type
     */
    public function type(): Type
    {
        return GraphQL::type('User');
    }

    /**
     * @return array[]
     */
    public function args(): array
    {
        return [
            'id' => [
                'name' => 'id',
                'type' =>  Type::nonNull(Type::int()),
                'rules' => ['required'],
                'description' => 'User id'
            ],
            'name' => [
                'name' => 'name',
                'type' => Type::nonNull(Type::string()),
                'rules' => ['required'],
                'description' => 'User name'
            ],
            'email' => [
                'name' => 'email',
                'type' =>  Type::nonNull(Type::string()),
                'rules' => ['required','email'],
                'description' => 'User email'
            ],
            'dateOfBirth' => [
                'name' => 'dateOfBirth',
                'type' =>  Type::nonNull(Type::string()),
                'rules' => ['required'],
                'description' => 'User date of birth'
            ],
            'isActive' => [
                'name' => 'isActive',
                'type' => Type::nonNull(Type::boolean()),
                'rules' => ['required'],
                'description' => 'Active user (true,false)'
            ],
        ];
    }

    /**
     * @param $rootValue
     * @param array $args
     * @return User
     * @throws Error
     */
    public function resolve($rootValue, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields): User
    {
        return $this->userService->update($args);
    }

}

Vegyünk fel mutációt user törléshez application/app/GraphQL/Mutation/DeleteUserMutation.php
 
<?php

namespace App\GraphQL\Mutations;

use App\GraphQL\Services\UserPhonesService;
use GraphQL\Error\Error;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
use App\Models\User;
use App\GraphQL\Services\UserService;

/**
 * Class DeleteUserMutation
 * @package App\GraphQL\Mutations
 */
class DeleteUserMutation extends Mutation
{
    /**
     * @var string[]
     */
    protected $attributes = [
        'name' => 'deleteUser',
        'description' => 'Delete user'
    ];

    /**
     * @var UserService
     */
    private UserService $userService;

    /**
     * @var UserPhonesService
     */
    private UserPhonesService $userPhonesService;

    public function __construct(UserService $userService, UserPhonesService $userPhonesService)
    {
        $this->userService = $userService;

        $this->userPhonesService = $userPhonesService;
    }

    /**
     * @return Type
     */
    public function type(): Type
    {
        return GraphQL::type('User');
    }

    /**
     * @return array[]
     */
    public function args(): array
    {
        return [

            'id' => [
                'name' => 'id',
                'type' => Type::nonNull(Type::int()),
                'rules' => ['required'],
                'description' => 'User id'
            ],

        ];
    }

    /**
     * @param $rootValue
     * @param array $args
     * @return User
     * @throws Error
     */
    public function resolve($rootValue, array $args): User
    {
        return $this->userService->destroy($args['id']);
    }
}

A user model nem tartalmaz semmi különbséget a rest api-hoz képest. A user service sem tartalmaz semmi izgalmasat, meghívjuk a repository-t, vagy elmentjük az adatokat stb.

Kipróbálni a végpontot a következő url-en tudjuk: http://localhost/graphql-playground



Pár példa a végpont használatára:
 
// Lista lekérdezése

query {
  users(limit: 2 page: 4, order: "desc", sortBy: "name")
  {
    data {
      id,
      name
    },
    total,
    per_page
  }
}

// Új felhasználó

mutation{
  newUser(
    name: "My user"
    email: "test@example.com"
    dateOfBirth: "1973-08-08"
    isActive: true
    phoneNumber: "20/111-11111"
  ){
    name
    email
  }
}

// Felhasználó módosítása

mutation
{
  updateUser(
    id: 11
    name: "Test User"
  ){
    name
    email
    dateOfBirth
    isActive
  }
}

// Felhasználó törlése

mutation
{
  deleteUser(
    id: 12
  ){
    name
    email
    dateOfBirth
    isActive
  }
}


Olvasnivaló:
https://github.com/rebing/graphql-laravel
https://github.com/supliu/laravel-graphql
https://lighthouse-php.com/tutorial/#the-models
https://packagist.org/packages/nuwave/lighthouse
https://www.toptal.com/graphql/laravel-graphql-server-tutorial
https://github.com/driesvints/graphql-shop
https://mobiosolutions.com/steps-to-build-graphql-server-from-scratch-in-laravel/
 
2021.05.08.