آموزش ساخت API با لاراول و GraphQL

GraphQL چیست

آموزش ساخت API با لاراول و GraphGL

GraphQL یک زبان کوئری نویسی برای کار با API ها و همچنین یک محیط اجرایی سمت سرور برای اجرای کوئری ها توسط type system هایی است که شما تعریف کرده اید.

GraphQL به هیچ نوع پایگاه داده یا موتور ذخیره سازی وابسته نیست و بجای آن از همان کدهایی که نوشته اید استفاده می کند.

GraphQL ابزاری است که باعث افزایش انعطاف پذیری فراخوانی API ها می شود و ما می توانیم از یک زبان کوئری همانند زبان کوئری که در هنگام کار با دیتابیس استفاده می کنیم، برای دسترسی به APIها استفاده کنیم. این یک قابلیت خیلی قدرتمند برای ساخت API های پیچیده است.

GraphQL یک رابط کاربری برای کمک به نوشتن کوئری ارائه کرده که از قابلیت تکمیل خودکار کدها برخوردار است و در هنگام نوشتن کوئری ها کمک زیادی به ما می کند.

برای درک مفهوم GraphQL تصویر زیر را مشاهده کنید، همان طور که می بینید GraphQL درخارج از لایه منطق تجاری برنامه اجرا می شود.

معماری GraphQL

مطالعه موردی: یک مثال درباره API لیست محصولات و لیست کاربران

شروع کار

1- نصب لاراول

ابتدا با دستور زیر آخرین نسخه لاراول را نصب کنید.

# run in terminal
composer global require "laravel/installer"
laravel new laravel-graphql

2- افزودن پکیج GraphQL

پکیج graphql-laravel را توسط کامپوزر نصب کنید، این پکیج قابلیت های زیادی برای یکپارچه کردن لاراول با GraphQL دارد.

3- ایجاد مدل

جدول مدل های product ،user_profile ،product_image را همانند زیر ایجاد کنید و روابط بین آنها را هم مانند تصویر زیر تعریف کنید.

ساخت مدل ها در لاراول

4- ایجاد query و Type در GraphQL

Query در GraphQL همانند تعریف مسیر endpoint در Restful Api ها است.

Query تنها برای دریافت داده ها استفاده می شود و برای انجام عملیات های create، update و delete از Mutationها استفاده می کنیم.

Typeها برای تعریف نوع فیلدهای query استفاده می شود. Typeها به ما کمک می کنند تا نوع فیلدهای نتایجی که از اجرای یک کوئری بدست می آید را فرمت بندی کنیم. برای مثال نوع های بولین، رشته ای، اعشاری و اعداد صحیح و ... . همچنین می توانیم نوع های سفارشی تعریف کنیم.

تصویر زیر ساختار دایرکتوری مربوط به query و Typeها را نمایش می دهد.

ساختار دایرکتوری های GraphQL در لاراول

در زیر کد کامل userQuery.php و usersType.php را آورده ایم.

<?php
namespace App\GraphQL\Query;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\SelectFields;
class UsersQuery extends Query
{
    protected $attributes = [
        'name' => 'Users Query',
        'description' => 'A query of users'
    ];
    public function type()
    {
        // result of query with pagination laravel
        return GraphQL::paginate('users');
    }
    
    // arguments to filter query
    public function args()
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::int()
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::string()
            ]
        ];
    }
    public function resolve($root, $args, SelectFields $fields)
    {
        $where = function ($query) use ($args) {
            if (isset($args['id'])) {
                $query->where('id',$args['id']);
            }
            if (isset($args['email'])) {
                $query->where('email',$args['email']);
            }
        };
        $user = User::with(array_keys($fields->getRelations()))
            ->where($where)
            ->select($fields->getSelect())
            ->paginate();
        return $user;
    }
}
<?php
namespace App\GraphQL\Type;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UsersType extends GraphQLType
{
    protected $attributes = [
        'name' => 'Users',
        'description' => 'A type',
        'model' => User::class, // define model for users type
    ];
    
    // define field of type
    public function fields()
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The id of the user'
            ],
            'email' => [
                'type' => Type::string(),
                'description' => 'The email of user'
            ],
            'name' => [
                'type' => Type::string(),
                'description' => 'The name of the user'
            ],
            // field relation to model user_profiles
            'user_profiles' => [
                'type' => GraphQL::type('user_profiles'),
                'description' => 'The profile of the user'
            ]
        ];
    }
    protected function resolveEmailField($root, $args)
    {
        return strtolower($root->email);
    }
}

بعد از ایجاد query و Typeها، باید آنها را در فایل config/graphql.php ثبت کنیم.

<?php
use App\GraphQL\Query\ProductsQuery;
use App\GraphQL\Query\UsersQuery;
use App\GraphQL\Type\ProductImagesType;
use App\GraphQL\Type\ProductsType;
use App\GraphQL\Type\UserProfilesType;
use App\GraphQL\Type\UsersType;
return [
    'prefix' => 'graphql',
    'routes' => 'query/{graphql_schema?}',
    'controllers' => \Rebing\GraphQL\GraphQLController::class . '@query',
    'middleware' => [],
    'default_schema' => 'default',
    // register query  
    'schemas' => [
        'default' => [
            'query' => [
                'users' => UsersQuery::class,
                'products' => ProductsQuery::class,
            ],
            'mutation' => [
            ],
            'middleware' => []
        ],
    ],
    // register types
    'types' => [
        'product_images' => ProductImagesType::class,
        'products'  => ProductsType::class,
        'user_profiles'  => UserProfilesType::class,
        'users'  => UsersType::class,
    ],
    'error_formatter' => ['\Rebing\GraphQL\GraphQL', 'formatError'],
    'params_key'    => 'params'
];

5- تست برنامه

ما می توانیم به راحتی از GraphQL برای دریافت نتایج از APIها استفاده کنیم.

چون هنگامی که شروع به کدنویسی کنیم قابلیت تکمیل خودکار کد در GraphQL به ما در کدنویسی کمک زیادی می کند و همچنین توسط برنامه postman می توانیم به راحتی APIها را فراخوانی کنیم. در زیر مثالی از تکمیل خودکار کد را می بینید.

قابلیت تکمیل خودکار کدها در GraphQL

و نتیجه کدهای بالا را در تصویر زیر می بینید:

نمایش خودکار کدها در GraphQL

 در این قسمت درباره Mutation و اعتبارسنجی Api توسط GraphQL توضیح خواهم داد.

GraphQL یک ابزار عالی برای ساخت راحت و دینامیک Api ها است و می توانید از این ابزار در برنامه های سمت کلاینتی که با ReactJs/ReactNative نوشته اید، استفاده کنید. همچنین کتابخانه های زیادی برای پشتیبانی از GraphQL ساخته شده اند.

ایجاد کلاس های Mutation

Mutation ها در حقیقت یک نوع (Type) برای درج داده در دیتابیس یا تغییر داده هایی که قبلاً در دیتابیس درج شده است.

۱ - افزودن Mutaion

در بخش قبلی دیدید که یک مدل به نام user داشتیم، حال می توانیم به راحتی دو کلاس Mutation، یکی برای افزودن کاربر جدید یعنی کلاس NewUserMutation.php و دیگری برای بروزرسانی کلاس user یعنی updateUserMutation.php ایجاد کنیم.

افزودن کلاس های Mutation به برنامه لاراول

در فایل NewUserMutation.php تابعی به نام args داریم که در آن نام فیلد ورودی و نوع فیلدهای ورودی مان را تعریف می کنیم: Type::nonNull یعنی پر کردن این فیلد اجباری است.

<?php
/**
 * Created by PhpStorm.
 * User: ardani
 * Date: 8/4/17
 * Time: 10:02 AM
 */
namespace App\GraphQL\Mutation;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Mutation;
use App\User;
class NewUserMutation extends Mutation
{
    protected $attributes = [
        'name' => 'NewUser'
    ];
    public function type()
    {
        return GraphQL::type('users');
    }
    public function args()
    {
        return [
            'name' => [
                'name' => 'name',
                'type' => Type::nonNull(Type::string())
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::nonNull(Type::string())
            ],
            'password' => [
                'name' => 'password',
                'type' => Type::nonNull(Type::string())
            ],
            'first_name' => [
                'name' => 'first_name',
                'type' => Type::nonNull(Type::string())
            ],
            'last_name' => [
                'name' => 'last_name',
                'type' => Type::string()
            ],
            'avatar' => [
                'name' => 'avatar',
                'type' => Type::string()
            ]
        ];
    }
    public function resolve($root, $args)
    {
        $args['password'] = bcrypt($args['password']);
        $user = User::create($args);
        if (!$user) {
            return null;
        }
        $user->user_profiles()->create($args);
        return $user;
    }
}

updateUserMutation.php برای بروزرسانی داده های کاربر استفاده می شود. تمام عملیات های درج و بروزرسانی هنوز هم توسط Eloquent انجام می شود و برای کوئری های پیچیده می توانید از Query Builder استفاده کنید.

<?php
/**
 * Created by PhpStorm.
 * User: ardani
 * Date: 8/4/17
 * Time: 10:02 AM
 */
namespace App\GraphQL\Mutation;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Mutation;
use App\User;
class UpdateUserMutation extends Mutation
{
    protected $attributes = [
        'name' => 'UpdateUser'
    ];
    public function type()
    {
        return GraphQL::type('users');
    }
    public function args()
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::nonNull(Type::int())
            ],
            'name' => [
                'name' => 'name',
                'type' => Type::nonNull(Type::string())
            ]
        ];
    }
    public function resolve($root, $args)
    {
        $user = User::find($args['id']);
        if (!$user) {
            return null;
        }
        $user->name = $args['name'];
        $user->save();
        return $user;
    }
}

2- اضافه کردن Mutationها در GraphQL

کلاس graphql.php که در فولدر config قرار گرفته را باز کنید و در قسمت Mutation، کلاس های Mutationیی که در مرحله قبل ساختید را به آن اضافه کنید.

اضافه کردن Mutationها در GraphQL

بعد از اضافه کردن کلاس Mutation آدرس http://127.0.0.1:8000/graphql-ui را در مرورگر باز کنید. با انجام این کار جزئیات Mutationها نمایش داده می شود.

۳- دمو

پیش نمایش Mutation ها در GraphQL

احراز هویت JWT در GraphQL

Json Web Token به اختصار JWT، استاندارد متن باز (RFC7519) و یک روش کامل است که انتقال امن اطلاعات بین بخش های مختلف را در یک شیء json تعریف می کند.

این اطلاعات می تواند به راحتی اعتبارسنجی شود، چون بصورت دیجیتالی امضا می شوند.

JWT ها توسط یک کلید محرمانه ( با الگوریتم HMAC) یا یک کلید خصوصی/ عمومی که با الگوریتم RSA رمزنگاری شده، امضا می شود.

1- نصب پکیج JWT

JWT ابزار مفیدی برای احراز هویت بدون حالت APIها است و توسط پکیج زیر به راحتی با لاراول یکپارچه می شود.

این پکیج را می توانید از این لینک دریافت کنید

برای آموزش نصب این پکیج این لینک را دنبال کنید.

2- ایجاد کنترلر و احراز هویت

ما از پارامتر Authorization:Bearer yourtokenhere در هدر تمام درخواست هایی که به GraphQL ارسال می شود استفاده می کنیم که در این پارامتر عبارت yourtokenhere با توکن (Token) کاربر که توسط پکیج jwt احراز هویت شده است، ایجاد می گردد.

ابتدا یک کنترلر برای احراز هویت ایجاد می کنیم.

<?php
/**
 * Created by PhpStorm.
 * User: ardani
 * Date: 8/4/17
 * Time: 11:18 AM
 */
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\JWTAuth;
class AuthenticateController extends Controller
{
    private $jwt;
    public function __construct(JWTAuth $jwt)
    {
        $this->jwt = $jwt;
    }
    public function authenticate(Request $request)
    {
        // grab credentials from the request
        $credentials = $request->only('email', 'password');
        try {
            // attempt to verify the credentials and create a token for the user
            if (! $token = $this->jwt->attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            // something went wrong whilst attempting to encode the token
            return response()->json(['error' => 'could_not_create_token'], 500);
        }
        // all good so return the token
        return response()->json(compact('token'));
    }
}

سپس کد زیر که برای login  استفاده می شود را به فایل web.php اضافه کنید.

Route::post(‘graphql/login’, ‘AuthenticateController@authenticate’);

3- ایجاد Query به همراه اعتبارسنجی

برای مثال ما از کوئری myProfile برای دسترسی به myProfile ایی که توسط JWT احراز هویت شود، استفاده می کنیم. ابتدا یک فایل به نام myProfileQuery.php ایجاد می کنیم.

<?php
namespace App\GraphQL\Query;
use App\User;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\SelectFields;
use Tymon\JWTAuth\Facades\JWTAuth;
class MyProfileQuery extends Query
{
    private $auth;
    protected $attributes = [
        'name' => 'My Profile Query',
        'description' => 'My Profile Information'
    ];
    public function authorize(array $args)
    {
        try {
            $this->auth = JWTAuth::parseToken()->authenticate();
        } catch (\Exception $e) {
            $this->auth = null;
        }
        return (boolean) $this->auth;
    }
    public function type()
    {
        return GraphQL::type('myprofile');
    }
    public function resolve($root, $args, SelectFields $fields)
    {
        $user = User::with(array_keys($fields->getRelations()))
            ->where('id', $this->auth->id)
            ->select($fields->getSelect())->first();
        return $user;
    }
}

عمل احراز هویت در متد authorize انجام می گیرد و ابتدا باید احراز هویت JWT را بررسی کنیم. در صورتی که هویت معتبر بود، ادامه فرآیند را انجام می دهیم، اما در صورتی که عمل احراز هویت ناموفق بود، پیام unauthorized را به عنوان نتیجه GraphQL بر می گردانیم.

یادتان باشد که حتماً این فایل کوئری را به config > graphql.php اضافه کنید.

احراز هویت با jwt

4- دمو

دموی احراز هویت با jwt

سورس کامل برنامه را از اینجا دریافت کنید.

نویسنده شوید
دیدگاه‌های شما (5 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مسعود
28 مهر 1397
برای localization در restful api laravel چطوری کار کنیم

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مسعود
28 مهر 1397
مهندس سلام میشه چند زبانه کردن در api را آموزش بدید

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مسعود
27 مهر 1397
من متوجه نشدم میشه مثال عملی در مورد api بزنید و در اختیاری کردن فیلد در متدد حخسف جواب نمیدهد

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

ali
25 مهر 1397
من یک apiساده نوشتم حالا کسی که میخواهد از این api استفاده کند چطوری استفاده می کند مثلا من api نوشتم که یک فیلدی را از دیتابیس بیرون میکشه و چاپ میکند طرف مقابل آن را در پروژه خودش چطوری فراخوانی میکنه آیا با route مورد نظر بیرون میکشه

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
26 مهر 1397
سلام وقت شما بخیر برای اینکار باید به سایت موردنظر دسترسی بدهید که بتواند ابتدا از API شما استفاده کند. در این حالت مفهومی تحت عنوان CORS مطرح می شود که با توجه به زبان برنامه نویسی ای که کار می کنید می توانید درون آن دسترسی ایجاد کنید. در مرحله بعدی کافیست Route موردنظر را در اختیار سایت هدف قرار دهید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مسعود
25 مهر 1397
سلام آقا سعید من یک سوالی داشتم در api نویسی من میخواهم بعضی از متعیر ها در urlها اختیاری کنم مثلا Route::post('/attributedel/{id}/{fa?}','AttributesController@delete1'); میدونم از ؟ باید استفاده کرد اما جواب نمیده وسوال یعدی این که من در api باید create , update,edit,delete را بسازم تا کسی خواست از روی این api در برنامه اش استفاده کنه بتونه هر کدی خواست را از دل دیتابیس بکشه بیرون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
26 مهر 1397
سلام وقت شما بخیر برای اختیاری کردن یک ورودی در api کافیست علامت سوال را در کنار آن قرار دهید. توجه به این نکته ضروری است که باید درون متد delete نیز مقدار این پارامتر را (در اینجا fa) به صورت null قرار دهید. به کد زیر توجه کنید: Route::get('user/{name?}', function ($name = null) { return $name; }); برای استفاده از API نیز به نظری که دوست دیگر ما (ali) پرسیده است مراجعه کنید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مسعود
28 مهر 1397
در متد post نمیشه

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.