การสร้างเซิร์ฟเวอร์ GraphQL ด้วย Laravel

เผยแพร่แล้ว: 2022-03-11

ในกรณีที่คุณยังไม่คุ้นเคย GraphQL เป็นภาษาคิวรีที่ใช้ในการโต้ตอบกับ API ของคุณ ซึ่งให้ประโยชน์บางประการเมื่อเทียบกับสถาปัตยกรรมทางเลือก เช่น REST GraphQL มีประโยชน์อย่างยิ่งเมื่อใช้เป็นปลายทางสำหรับแอปพลิเคชันมือถือและหน้าเดียว GraphQL ช่วยให้คุณค้นหาข้อมูลที่ซ้อนกันและข้อมูลที่เกี่ยวข้องในคำขอได้อย่างง่ายดาย ช่วยให้นักพัฒนาได้รับข้อมูลที่ต้องการในการเดินทางไปเซิร์ฟเวอร์ครั้งเดียว

Laravel เป็นเฟรมเวิร์กเว็บ PHP ที่ได้รับความนิยม มีเครื่องมือในตัวจำนวนมากเพื่อให้แอปพลิเคชันทำงานได้อย่างรวดเร็ว แต่ยังช่วยให้นักพัฒนาสามารถสลับการใช้งานของตนเองสำหรับอินเทอร์เฟซในตัวของ Laravel เมื่อต้องการ

แม้ว่าชุมชนโดยรอบทั้ง GraphQL และ Laravel จะเติบโตขึ้นอย่างมากเนื่องจากเป็นโอเพ่นซอร์ส แต่เอกสารที่อธิบายวิธีการใช้เทคโนโลยีทั้งสองนี้ร่วมกันก็ยังค่อนข้างหายาก

ในบทช่วยสอนนี้ ฉันจะแสดงวิธีสร้างเซิร์ฟเวอร์ GraphQL ของคุณเองโดยใช้ Laravel

ภาพรวมโครงการ

ภาพประกอบภาพรวมเซิร์ฟเวอร์ GraphQL

ก่อนที่เราจะเริ่มต้น เราจะต้องทำความคุ้นเคยกับโครงการที่เรากำลังพยายามสร้าง ในการทำเช่นนั้น เราจะกำหนดทรัพยากรของเราและสร้างสคีมา GraphQL ซึ่งเราจะใช้เพื่อให้บริการ API ของเราในภายหลัง

ทรัพยากรโครงการ

แอปพลิเคชันของเราจะประกอบด้วยสองแหล่งข้อมูล: บทความ และ ผู้ใช้ ทรัพยากรเหล่านี้จะถูกกำหนดเป็นประเภทวัตถุในสคีมา GraphQL ของเรา:

 type User { id: ID! name: String! email: String! articles: [Article!]! } type Article { id: ID! title: String! content: String! author: User! }

เมื่อดูที่สคีมา เราจะเห็นว่าเรามีความสัมพันธ์แบบหนึ่งต่อกลุ่มระหว่างสองอ็อบเจ็กต์ของเรา ผู้ใช้สามารถเขียนบทความได้หลายบทความ และบทความหนึ่งมีผู้เขียน (ผู้ใช้) ที่ได้รับมอบหมาย

ตอนนี้เราได้กำหนดประเภทวัตถุแล้ว เราจึงต้องการวิธีสร้างและสืบค้นข้อมูลของเรา ดังนั้นมากำหนดวัตถุการสืบค้นและการกลายพันธุ์ของเรา:

 type Query { user(id: ID!): User users: [User!]! article(id: ID!): Article articles: [Article!]! } type Mutation { createUser(name: String!, email: String!, password: String!): User createArticle(title: String!, content: String!): Article }

การตั้งค่าโครงการ Laravel ของเรา

ตอนนี้เราได้กำหนดสคีมา GraphQL แล้ว มาเริ่มโปรเจ็กต์ Laravel กัน เริ่มต้นด้วยการสร้าง Laravel ใหม่ผ่านโปรเจ็กต์ Composer:

 $ composer create-project --prefer-dist laravel/laravel laravel-graphql

เพื่อให้แน่ใจว่าทุกอย่างทำงานได้ ให้เปิดเครื่องเซิร์ฟเวอร์ของเราและตรวจดูให้แน่ใจว่าเราเห็นหน้าเริ่มต้นของ Laravel:

 $ cd laravel-graphql $ php artisan serve Laravel development server started: <http://127.0.0.1:8000>

โมเดลฐานข้อมูลและการย้ายข้อมูล

สำหรับวัตถุประสงค์ของบทความนี้ เราจะใช้ SQLite เรามาทำการเปลี่ยนแปลงต่อไปนี้กับไฟล์ .env เริ่มต้นกัน:

 DB_CONNECTION=sqlite # DB_HOST= # DB_PORT= # DB_DATABASE=database.sqlite # DB_USERNAME= # DB_PASSWORD=

ต่อไป มาสร้างไฟล์ฐานข้อมูลของเรากัน:

 $ touch ./database/database.sqlite

Laravel มาพร้อมกับโมเดลผู้ใช้และไฟล์การย้ายข้อมูลพื้นฐานบางส่วน มาเพิ่มคอลัมน์ api_token อย่างรวดเร็วในไฟล์การโยกย้าย CreateUsersTable ที่ Laravel มอบให้เรา:

 /database/migrations/XXXX_XX_XX_000000_create_users_table.php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration { /** * Run the migrations. */ public function up() { Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->string('api_token', 80)->unique()->nullable()->default(null); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down() { Schema::dropIfExists('users'); } }

เราจะวนกลับไปที่คอลัมน์เพิ่มเติมนี้ในภายหลังในบทความเมื่อเราได้รับการอนุญาต ตอนนี้ ไปข้างหน้าและสร้างแบบจำลองบทความของเราและไฟล์การโยกย้ายเพื่อสร้างตารางที่เกี่ยวข้อง:

 $ php artisan make:model Article -m

หมายเหตุ: ตัวเลือก -m จะสร้างไฟล์การโยกย้ายสำหรับโมเดลบทความที่สร้างขึ้นใหม่ของเรา

มาทำการปรับเปลี่ยนบางอย่างกับไฟล์การโยกย้ายที่สร้างขึ้น:

 use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArticlesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('articles', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedBigInteger('user_id'); $table->string('title'); $table->text('content'); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('articles'); } }

เราได้เพิ่มคีย์นอกที่ชี้ไปที่ id ในตาราง users ของเรา เช่นเดียวกับคอลัมน์ title และ content ที่เรากำหนดไว้ในสคีมา GraphQL

ตอนนี้เราได้กำหนดไฟล์การโยกย้ายแล้ว ให้ดำเนินการกับฐานข้อมูลของเรา:

 $ php artisan migrate

ต่อไป มาอัปเดตโมเดลของเราโดยกำหนดความสัมพันธ์ที่จำเป็น:

 app/User.php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; // ... /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function articles() { return $this->hasMany(Article::class); } }
 app/Article.php namespace App; use Illuminate\Database\Eloquent\Model; class Article extends Model { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'content', ]; /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { return $this->belongsTo(User::class); } }

การเพาะฐานข้อมูล

ตอนนี้เราได้ตั้งค่าแบบจำลองและการย้ายข้อมูลแล้ว มาเริ่มต้นฐานข้อมูลของเรากัน เราจะเริ่มต้นด้วยการสร้างคลาส seeder สำหรับ articles และตาราง users ของเรา:

 $ php artisan make:seeder UsersTableSeeder $ php artisan make:seeder ArticlesTableSeeder

ต่อไป มาตั้งค่าให้แทรกข้อมูลจำลองลงในฐานข้อมูล SQLite ของเรา:

 database/seeds/UsersTableSeeder.php use App\User; use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { /** * Run the database seeds. */ public function run() { \App\User::truncate(); $faker = \Faker\Factory::create(); $password = bcrypt('secret'); \App\User::create([ 'name' => $faker->name, 'email' => '[email protected]', 'password' => $password, ]); for ($i = 0; $i < 10; ++$i) { \App\User::create([ 'name' => $faker->name, 'email' => $faker->email, 'password' => $password, ]); } } }
 database/seeds/ArticlesTableSeeder.php use App\Article; use Illuminate\Database\Seeder; class ArticlesTableSeeder extends Seeder { /** * Run the database seeds. */ public function run() { \App\Article::truncate(); \App\Article::unguard(); $faker = \Faker\Factory::create(); \App\User::all()->each(function ($user) use ($faker) { foreach (range(1, 5) as $i) { \App\Article::create([ 'user_id' => $user->id, 'title' => $faker->sentence, 'content' => $faker->paragraphs(3, true), ]); } }); } }
 /database/seeds/DatabaseSeeder.php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(UsersTableSeeder::class); $this->call(ArticlesTableSeeder::class); } }

สุดท้าย ให้ดำเนินการต่อไปและเรียกใช้ฐานข้อมูลของเราเพื่อนำข้อมูลบางส่วนเข้าสู่ฐานข้อมูลของเรา:

 $ php artisan db:seed

Laravel Lighthouse และเซิร์ฟเวอร์ GraphQL

ตอนนี้เราได้สร้างฐานข้อมูลและโมเดลแล้ว ถึงเวลาที่จะเริ่มสร้างเซิร์ฟเวอร์ GraphQL ของเรา ปัจจุบัน มีวิธีแก้ปัญหาหลายอย่างสำหรับ Laravel แต่สำหรับบทความนี้ เราจะใช้ Lighthouse

Lighthouse เป็นแพ็คเกจที่ฉันสร้างขึ้นเมื่อไม่กี่ปีที่ผ่านมา และเพิ่งได้รับการสนับสนุนที่น่าทึ่งจากชุมชนที่กำลังเติบโตรอบๆ ช่วยให้นักพัฒนาสามารถตั้งค่าเซิร์ฟเวอร์ GraphQL ได้อย่างรวดเร็วโดยใช้ Laravel โดยมีต้นแบบเพียงเล็กน้อย ในขณะที่ยังมีความยืดหยุ่นเพียงพอที่จะให้นักพัฒนาสามารถปรับแต่งเซิร์ฟเวอร์ให้เหมาะสมกับความต้องการของแทบทุกโครงการ

ภาพประกอบ Laravel Lighthouse และ GraphQL Server

เริ่มต้นด้วยการดึงแพ็คเกจเข้าสู่โครงการของเรา:

 $ composer require nuwave/lighthouse:"3.1.*"

ต่อไป มาเผยแพร่ไฟล์การกำหนดค่าของ Lighthouse:

 $ php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=config

หมายเหตุ: คุณยังสามารถเลือกที่จะเผยแพร่ไฟล์สคีมาเริ่มต้นของ Lighthouse ได้โดยเพียงแค่ลบ --tag=config ตัวเลือก แต่สำหรับวัตถุประสงค์ของบทความนี้ เราจะสร้างไฟล์สคีมาตั้งแต่ต้น

ถ้าเราดูที่ไฟล์ config/lighthouse.php คุณจะสังเกตเห็นการตั้งค่าที่ใช้ในการลงทะเบียนไฟล์สคีมาของเรากับ Lighthouse:

 'schema' => [ 'register' => base_path('graphql/schema.graphql'), ],

มาสร้างไฟล์สคีมาและตั้งค่าประเภทออบเจกต์ผู้ใช้และคิวรีของเรากัน:

 $ mkdir graphql $ touch ./graphql/schema.graphql /graphql/schema.graphql type User { id: ID! name: String! email: String! } type Query { user(id: ID! @eq): User @find users: [User!]! @all }

คุณจะสังเกตเห็นว่าสคีมาของเราดูคล้ายกับที่เรากำหนดไว้ก่อนหน้านี้ ยกเว้นว่าเราได้เพิ่มตัวระบุที่เรียกว่าคำสั่งสคีมา

สละเวลาสักครู่เพื่อแยกย่อยสคีมาที่กำหนดไว้ของเรา คำจำกัดความแรกของเราคือ ประเภทวัตถุที่ เรียกว่า User ซึ่งมีความสัมพันธ์กับรูปแบบวาทศิลป์ของ App\User เรากำหนด id name และ email เป็นฟิลด์ที่สามารถสอบถามจากโมเดล User ของเรา อีกทางหนึ่งหมายความว่าคอลัมน์ password , created_at และ updated_at เป็นฟิลด์ที่ไม่สามารถสืบค้นจาก API ของเราได้

ต่อไปเรามีประเภทการ Query ซึ่งเป็นจุดเริ่มต้นใน API ของเราและสามารถใช้เพื่อค้นหาข้อมูล ฟิลด์แรกของเราคือฟิลด์ users ซึ่งส่งคืนอาร์เรย์ของประเภทอ็อบเจ็กต์ User คำสั่ง @all บอกให้ Lighthouse เรียกใช้การสืบค้น Eloquent โดยใช้โมเดล User ของเราและรับผลลัพธ์ทั้งหมด นี่จะเหมือนกับการรันสิ่งต่อไปนี้:

 $users = \App\User::all();

หมายเหตุ: Lighthouse รู้จักมองหาโมเดลในเนมสเปซ \App\User เนื่องจากตัวเลือก namespaces ที่กำหนดไว้ในไฟล์การกำหนดค่า

ฟิลด์ที่กำหนดที่สองของเราในประเภทการสืบค้นของเราคือ call user ซึ่งใช้ id เป็นอาร์กิวเมนต์และส่งกลับประเภทอ็อบเจ็กต์ User เดียว เรายังได้เพิ่มสองคำสั่งเพื่อช่วยให้ Lighthouse สร้างการสืบค้นข้อมูลให้เราโดยอัตโนมัติและส่งคืนโมเดล User เดียว คำสั่ง @eq บอกให้ Lighthouse เพิ่มตำแหน่งในคอลัมน์ id ของเรา และคำสั่ง @find สั่งให้ Lighthouse ส่งคืนผลลัพธ์เดียว ในการเขียนแบบสอบถามนี้โดยใช้ตัวสร้างแบบสอบถามของ Laravel จะมีลักษณะดังนี้:

 $user = \App\User::where('id', $args['id'])->first();

การสืบค้น GraphQL API ของเรา

ตอนนี้เรามีข้อมูลเชิงลึกเล็กน้อยว่า Lighthouse ใช้สคีมาของเราในการสร้างการสืบค้นอย่างไร ให้เรียกใช้เซิร์ฟเวอร์ของเราและเริ่มสืบค้นข้อมูล เราจะเริ่มต้นด้วยการเรียกใช้เซิร์ฟเวอร์ของเรา:

 $ php artisan serve Laravel development server started: <http://127.0.0.1:8000>

ในการสอบถามจุดปลาย GraphQL คุณสามารถเรียกใช้คำสั่ง cURL ในเทอร์มินัลหรือไคลเอนต์มาตรฐาน เช่น บุรุษไปรษณีย์ อย่างไรก็ตาม เพื่อให้ได้รับประโยชน์อย่างเต็มที่จาก GraphQL (เช่น การเติมข้อความอัตโนมัติ การเน้นข้อผิดพลาด เอกสารประกอบ ฯลฯ เราจะใช้ GraphQL Playground (เผยแพร่การดาวน์โหลดที่นี่)

เมื่อเริ่มต้น Playground ให้คลิกที่แท็บ "URL Endpoint" แล้วพิมพ์ http://localhost:8000/graphql เพื่อชี้ GraphQL Playground ไปที่เซิร์ฟเวอร์ของเรา ทางด้านซ้ายของตัวแก้ไข เราสามารถสืบค้นข้อมูลของเราได้ ดังนั้นเรามาเริ่มด้วยการขอผู้ใช้ทั้งหมดที่เรา seed ฐานข้อมูลด้วย:

 { users { id email name } }

เมื่อคุณกดปุ่มเล่นตรงกลาง IDE (หรือคลิก Ctrl+Enter ) คุณจะเห็นเอาต์พุต JSON ของเซิร์ฟเวอร์ของเราทางด้านขวา ซึ่งจะมีลักษณะดังนี้:

 { "data": { "users": [ { "id": "1", "email": "[email protected]", "name": "Carolyn Powlowski" }, { "id": "2", "email": "[email protected]", "name": "Elouise Raynor" }, { "id": "3", "email": "[email protected]", "name": "Mrs. Dejah Wiza" }, ... ] } }

หมายเหตุ: เนื่องจากเราใช้ Faker ในการ seed ฐานข้อมูลของเรา ข้อมูลในฟิลด์ email และ name จะแตกต่างกัน

ตอนนี้ เรามาลองค้นหาผู้ใช้คนเดียวกัน:

 { user(id: 1) { email name } }

และเราจะได้รับผลลัพธ์ต่อไปนี้สำหรับผู้ใช้คนเดียว:

 { "data": { "user": { "email": "[email protected]", "name": "Carolyn Powlowski" } } }

การสืบค้นข้อมูลแบบนี้เป็นสิ่งที่ดีในการเริ่มต้น แต่ไม่น่าเป็นไปได้อย่างยิ่งที่คุณจะอยู่ในโครงการที่คุณเคยต้องการสืบค้นข้อมูล ทั้งหมด ของคุณ ดังนั้นเรามาลองเพิ่มการแบ่งหน้ากัน เมื่อดูคำสั่งในตัวที่หลากหลายของ Lighthouse เรามีคำสั่ง @paginate ที่พร้อมให้เราใช้งาน ดังนั้นเรามาอัปเดตออบเจกต์การสืบค้นของสคีมาดังนี้:

 type Query { user(id: ID! @eq): User @find users: [User!]! @paginate }

หากเราโหลด GraphQL Playground ใหม่ ( Ctrl/Cmd + R ) และลองค้นหา users ของเราอีกครั้ง คุณจะสังเกตเห็นว่าเราได้รับข้อความแสดงข้อผิดพลาดที่ระบุว่า Cannot query field "id" on type "UserPaginator" เกิดอะไรขึ้น เบื้องหลัง Lighthouse จัดการสคีมาของเราเพื่อให้เราได้ชุดของผลลัพธ์ที่มีการแบ่งหน้า และทำโดยการเปลี่ยนประเภทการส่งคืนของฟิลด์ users ของเรา

มาดูกันดีกว่าโดยการตรวจสอบสคีมาของเราในแท็บ "เอกสาร" ของ GraphQL Playground หากคุณดูที่ฟิลด์ users ระบบจะส่งคืน UserPaginator ซึ่งส่งคืนอาร์เรย์ของผู้ใช้และประเภท PaginatorInfo ที่ Lighthouse กำหนด:

 type UserPaginator { paginatorInfo: PaginatorInfo! data: [User!]! } type PaginatorInfo { count: Int! currentPage: Int! firstItem: Int hasMorePages: Boolean! lastItem: Int lastPage: Int! perPage: Int! total: Int! }

หากคุณคุ้นเคยกับการแบ่งหน้าในตัวของ Laravel ฟิลด์ที่มีอยู่ในประเภท PaginatorInfo อาจจะดูคุ้นเคยสำหรับคุณมาก ดังนั้น ในการสอบถามผู้ใช้สองคน รับจำนวนผู้ใช้ทั้งหมดในระบบ และตรวจสอบว่าเรามีหน้าที่ต้องวนซ้ำอีก เราจะส่งข้อความค้นหาต่อไปนี้:

 { users(count:2) { paginatorInfo { total hasMorePages } data { id name email } } }

ซึ่งจะให้คำตอบแก่เราดังต่อไปนี้:

 { "data": { "users": { "paginatorInfo": { "total": 11, "hasMorePages": true }, "data": [ { "id": "1", "name": "Carolyn Powlowski", "email": "[email protected]" }, { "id": "2", "name": "Elouise Raynor", "email": "[email protected]" }, ] } } }

ความสัมพันธ์

โดยทั่วไป เมื่อพัฒนาแอปพลิเคชัน ข้อมูลส่วนใหญ่ของคุณมีความเกี่ยวข้องกัน ในกรณีของเรา User สามารถเขียน Articles ได้มากมาย ดังนั้นให้เพิ่มความสัมพันธ์นั้นกับประเภทผู้ใช้ของเราและกำหนดประเภท Article ของเรา:

 type User { id: ID! name: String! email: String! articles: [Article!]! @hasMany } type Article { id: ID! title: String! content: String! }

ที่นี่ เราใช้ Lighthouse อื่นที่มีสคีมา directive @hasMany ซึ่งบอก Lighthouse โมเดล User ของเรามี \Illuminate\Database\Eloquent\Relations\HasMany กับโมเดล Article

ตอนนี้ เรามาสอบถามความสัมพันธ์ที่กำหนดขึ้นใหม่ของเรากัน:

 { user(id:1) { articles { id title } } }

สิ่งนี้จะให้คำตอบแก่เราดังต่อไปนี้:

 { "data": { "user": { "articles": [ { "id": "1", "title": "Aut velit et temporibus ut et tempora sint." }, { "id": "2", "title": "Voluptatem sed labore ea voluptas." }, { "id": "3", "title": "Beatae sit et maxime consequatur et natus totam." }, { "id": "4", "title": "Corrupti beatae cumque accusamus." }, { "id": "5", "title": "Aperiam quidem sit esse rem sed cupiditate." } ] } } }

สุดท้าย เรามาย้อนความสัมพันธ์ของเราและเพิ่มความสัมพันธ์ระหว่าง author กับประเภทอ็อบเจกต์ Article ของเราโดยใช้คำสั่งสคีมา @belongsTo ของ Lighthouse รวมถึงอัปเดต Query ของเรา:

 type Article { id: ID! title: String! content: String! author: User! @belongsTo(relation: "user") } type Query { user(id: ID! @eq): User @find users: [User!]! @paginate article(id: ID! @eq): Article @find articles: [Article!]! @paginate }

คุณจะเห็นว่าเราได้เพิ่มอาร์กิวเมนต์ relation ที่เป็นทางเลือกให้กับคำสั่ง @belongsTo การดำเนินการนี้จะบอกให้ Lighthouse ใช้ความสัมพันธ์ user ของโมเดล Articles และกำหนดให้กับฟิลด์ author

ตอนนี้ เรามาค้นหารายชื่อบทความและหาผู้แต่งที่เกี่ยวข้องกัน:

 { articles(count:2) { paginatorInfo { total hasMorePages } data { id title author { name email } } } }

และเราควรได้รับสิ่งต่อไปนี้จากเซิร์ฟเวอร์ของเรา:

 { "data": { "articles": { "paginatorInfo": { "total": 55, "hasMorePages": true }, "data": [ { "id": "1", "title": "Aut velit et temporibus ut et tempora sint.", "author": { "name": "Carolyn Powlowski", "email": "[email protected]" } }, { "id": "2", "title": "Voluptatem sed labore ea voluptas.", "author": { "name": "Carolyn Powlowski", "email": "[email protected]" } } ] } } }

การกลายพันธุ์ของ GraphQL

ตอนนี้เราสามารถสืบค้นข้อมูลของเราได้แล้ว มาสร้างการกลายพันธุ์เพื่อสร้างผู้ใช้และบทความใหม่ เราจะเริ่มต้นด้วยรูปแบบผู้ใช้ของเรา:

 type Mutation { createUser( name: String! email: String! @rules(apply: ["email", "unique:users"]) password: String! @bcrypt @rules(apply: ["min:6"]) ): User @create }

ตอนนี้ เรามาทำลายนิยามสคีมานี้กัน เราได้สร้างการกลายพันธุ์ที่เรียกว่า createUser ซึ่งรับสามอาร์กิวเมนต์ ( name , email และ password ) เราใช้คำสั่ง @rules กับอาร์กิวเมนต์ email และ password ของเรา สิ่งนี้อาจดูคุ้นเคยเล็กน้อยเพราะคล้ายกับตรรกะการตรวจสอบที่ Laravel จัดเตรียมไว้สำหรับคอนโทรลเลอร์

ต่อไป เราได้แนบคำสั่ง @bcrypt เข้ากับฟิลด์ password ของเรา การดำเนินการนี้จะเข้ารหัสรหัสผ่านก่อนที่จะส่งไปยังโมเดลที่สร้างขึ้นใหม่

สุดท้าย เพื่อช่วยเราสร้างโมเดลใหม่ Lighthouse ได้จัดเตรียมคำสั่ง @create schema ซึ่งจะรับข้อโต้แย้งที่เรากำหนดและสร้างโมเดลใหม่ การใช้ตรรกะเดียวกันในคอนโทรลเลอร์จะมีลักษณะดังนี้:

 namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller { /** * Create a new user. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $data = $this->validate($request, [ 'email' => ['email', 'unique:users'], 'password' => ['min:6'] ]); $user = \App\User::create($data); return response()->json(['user' => $user]); } }

ตอนนี้เราได้ตั้งค่าฟิลด์การกลายพันธุ์ของ createUser แล้ว ให้เรียกใช้ใน GraphQL Playground ด้วยสิ่งต่อไปนี้:

 mutation { createUser( name:"John Doe" email:"[email protected]" password: "secret" ) { id name email } }

เราควรจะได้รับผลลัพธ์ต่อไปนี้:

 { "data": { "createUser": { "id": "12", "name": "John Doe", "email": "[email protected]" } } }

การตรวจสอบและการอนุญาต GraphQL

เนื่องจากเราจำเป็นต้องเพิ่ม user_id ให้กับโมเดล Article ของเรา ตอนนี้จึงเป็นเวลาที่ดีในการตรวจสอบสิทธิ์และการอนุญาตใน GraphQL/Lighthouse

ข้อความแสดงแทนรูปภาพ

ในการตรวจสอบสิทธิ์ผู้ใช้ เราจำเป็นต้องจัดเตรียม api_token ให้พวกเขา ดังนั้นเรามาสร้างการกลายพันธุ์เพื่อจัดการกับสิ่งนั้น แล้วเราจะเพิ่มคำสั่ง @field เพื่อชี้ Lighthouse ไปยังตัวแก้ไขแบบกำหนดเองซึ่งจะจัดการกับตรรกะ เราตั้งค่าตัวแก้ไขในรูปแบบเดียวกับการกำหนดตัวควบคุมใน resolver โดยใช้อาร์กิวเมนต์ตัวแก้ไข

ด้วยคำสั่ง @field ที่กำหนดไว้ด้านล่าง เรากำลังแจ้ง Lighthouse เมื่อมีการเรียกใช้การกลายพันธุ์การ login ใช้วิธี createToken ในคลาส App\GraphQL\Mutations\AuthMutator ของเรา:

 type Mutation { # ... login( email: String! password: String! ): String @field(resolver: "AuthMutator@resolve") }

หมายเหตุ: คุณไม่จำเป็นต้องรวมเนมสเปซทั้งหมดที่นี่ ในไฟล์กำหนดค่า lighthouse.php คุณจะเห็นว่าเรามีเนมสเปซที่กำหนดไว้สำหรับการกลายพันธุ์ของเราที่ตั้งค่าเป็น App\\GraphQL\\Mutations แล้ว อย่างไรก็ตาม คุณสามารถใช้เนมสเปซแบบเต็มได้หากต้องการ

ลองใช้ตัวสร้างของ Lighthouse เพื่อสร้างคลาส mutator ใหม่:

 $ php artisan lighthouse:mutation AuthMutator

ต่อไป มาอัปเดตฟังก์ชันตัวแก้ไขของเราดังนี้:

 namespace App\GraphQL\Mutations; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Illuminate\Support\Facades\Auth; use GraphQL\Type\Definition\ResolveInfo; use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; class AuthMutator { /** * Return a value for the field. * * @param null $rootValue Usually contains the result returned from the parent field. In this case, it is always `null`. * @param mixed[] $args The arguments that were passed into the field. * @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context Arbitrary data that is shared between all fields of a single query. * @param \GraphQL\Type\Definition\ResolveInfo $resolveInfo Information about the query itself, such as the execution state, the field name, path to the field from the root, and more. * @return mixed */ public function resolve($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) { $credentials = Arr::only($args, ['email', 'password']); if (Auth::once($credentials)) { $token = Str::random(60); $user = auth()->user(); $user->api_token = $token; $user->save(); return $token; } return null; } }

ตอนนี้เราได้ตั้งค่าตัวแก้ไขแล้ว มาทดสอบกันและลองรับโทเค็น API โดยใช้การกลายพันธุ์ต่อไปนี้ใน GraphQL Playground:

 mutation { login(email:"[email protected]", password:"secret") }

เราควรจะได้รับโทเค็นที่ส่งกลับมาให้เราดังนี้:

 { "data": { "login": "VJCz1DCpmdvB9WatqvWbXBP2RN8geZQlrQatUnWIBJCdbAyTl3UsdOuio3VE" } }

หมายเหตุ: อย่าลืมคัดลอกโทเค็นที่ส่งคืนจากการกลายพันธุ์ของล็อกอิน เพื่อให้เราสามารถใช้งานได้ในภายหลัง

ต่อไป มาเพิ่มฟิลด์แบบสอบถามซึ่งจะส่งคืนผู้ใช้ที่ตรวจสอบสิทธิ์เพื่อให้แน่ใจว่าตรรกะของเราใช้งานได้ เราจะเพิ่มฟิลด์ที่โทร me และใช้คำสั่ง @auth ของ Lighthouse เพื่อส่งคืนผู้ใช้ที่ได้รับการตรวจสอบสิทธิ์ในปัจจุบัน นอกจากนี้เรายังจะตั้งค่าอาร์กิวเมนต์ guard เท่ากับ api เนื่องจากเราจะตรวจสอบผู้ใช้อย่างไร

 type Query { # ... me: User @auth(guard: "api") }

ตอนนี้ขอเรียกใช้แบบสอบถาม ใน GraphQL Playground คุณสามารถตั้งค่าส่วนหัวของคำขอได้โดยดับเบิลคลิกที่แท็บ "Http Headers" ที่ด้านล่าง เราเพิ่มส่วนหัวด้วยวัตถุ JSON ดังนั้นหากต้องการเพิ่มโทเค็นผู้ถือในแต่ละคำขอ คุณจะต้องเพิ่มสิ่งต่อไปนี้:

 { "Authorization": "Bearer VJCz1DCpmdvB9WatqvWbXBP2RN8geZQlrQatUnWIBJCdbAyTl3UsdOuio3VE" }

หมายเหตุ: แทนที่โทเค็นผู้ถือด้วยโทเค็นที่คุณได้รับเมื่อเรียกใช้แบบสอบถามการ เข้าสู่ระบบ

ตอนนี้มาเรียกใช้แบบสอบถาม me :

 { me { email articles { id title } } }

เราควรจะได้ผลลัพธ์ที่มีลักษณะดังนี้:

 { "data": { "me": { "email": "[email protected]", "articles": [ { "id": "1", "title": "Rerum perspiciatis et quos occaecati exercitationem." }, { "id": "2", "title": "Placeat quia cumque laudantium optio voluptatem sed qui." }, { "id": "3", "title": "Optio voluptatem et itaque sit animi." }, { "id": "4", "title": "Excepturi in ad qui dolor ad perspiciatis adipisci." }, { "id": "5", "title": "Qui nemo blanditiis sed fugit consequatur." } ] } } }

มิดเดิลแวร์

ตอนนี้เราทราบแล้วว่าการรับรองความถูกต้องของเราทำงานอย่างถูกต้อง มาสร้างการกลายพันธุ์ครั้งสุดท้ายเพื่อสร้างบทความโดยใช้ผู้ใช้ที่รับรองความถูกต้องในปัจจุบัน เราจะใช้คำสั่ง @field เพื่อชี้ Lighthouse ไปที่ตัวแก้ไขของเรา และเราจะรวมคำสั่ง @middleware เพื่อให้แน่ใจว่าผู้ใช้เข้าสู่ระบบ

 type Mutation { # ... createArticle(title: String!, content: String!): Article @field(resolver: "ArticleMutator@create") @middleware(checks: ["auth:api"]) }

ขั้นแรก ให้สร้างคลาสการกลายพันธุ์:

 $ php artisan lighthouse:mutation ArticleMutator

ต่อไป มาอัปเดต mutator ด้วยตรรกะต่อไปนี้:

 namespace App\GraphQL\Mutations; use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; class ArticleMutator { /** * Return a value for the field. * * @param null $rootValue * @param mixed[] $args * @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context * @return mixed */ public function create($rootValue, array $args, GraphQLContext $context) { $article = new \App\Article($args); $context->user()->articles()->save($article); return $article; } }

หมายเหตุ: เราเปลี่ยนชื่อฟังก์ชัน resolve เริ่มต้นเพื่อ create คุณไม่จำเป็นต้องสร้างคลาสใหม่สำหรับตัวแก้ไขทุกตัว แต่คุณสามารถจัดกลุ่มตรรกะเข้าด้วยกันได้หากเหมาะสมกว่า

สุดท้าย ให้เรียกใช้การกลายพันธุ์ใหม่ของเราและตรวจสอบผลลัพธ์ อย่าลืมเก็บส่วนหัวการให้ Authorization จากข้อความค้นหาก่อนหน้าของเราในแท็บ "ส่วนหัว HTTP":

 mutation { createArticle( title:"Building a GraphQL Server with Laravel" content:"In case you're not currently familiar with it, GraphQL is a query language used to interact with your API..." ) { id author { id email } } }

เราควรจะได้รับผลลัพธ์ต่อไปนี้:

 { "data": { "createArticle": { "id": "56", "author": { "id": "1", "email": "[email protected]" } } } }

ห่อ

ในการสรุป เราได้ใช้ประโยชน์จาก Lighthouse เพื่อสร้างเซิร์ฟเวอร์ GraphQL สำหรับโครงการ Laravel ของเรา เราใช้คำสั่งสคีมาที่สร้างขึ้นบางส่วน สร้างการสืบค้นและการกลายพันธุ์ และจัดการการให้สิทธิ์และการตรวจสอบสิทธิ์

Lighthouse ช่วยให้คุณทำสิ่งต่างๆ ได้มากขึ้น (เช่น อนุญาตให้คุณสร้างคำสั่งสคีมาที่กำหนดเอง) แต่สำหรับวัตถุประสงค์ของบทความนี้ เรายึดติดอยู่กับพื้นฐาน และเราสามารถทำให้เซิร์ฟเวอร์ GraphQL ทำงานได้โดยใช้ต้นแบบเพียงเล็กน้อย

ครั้งต่อไปที่คุณต้องตั้งค่า API สำหรับแอปพลิเคชันมือถือหรือหน้าเดียว อย่าลืมพิจารณา GraphQL เป็นวิธีการสืบค้นข้อมูลของคุณ!

ที่เกี่ยวข้อง: การ ตรวจสอบสิทธิ์ผู้ใช้แบบเต็มและการควบคุมการเข้าถึง – บทช่วยสอน Laravel Passport, Pt. 1