はじめに
現代のWebアプリケーション開発において、クライアントとサーバー間のデータ通信は非常に重要な要素です。従来のRESTful APIは広く使われていますが、柔軟性や効率性の面で課題も存在します。そこで注目されているのがGraphQLです。
GraphQLはFacebookが開発したAPIのためのクエリ言語であり、クライアントが必要なデータを正確に指定できるという特徴があります。この記事では、Laravel(PHP)とGraphQLを組み合わせてAPIを開発する方法について解説します。
GraphQLとは
GraphQLは、APIのためのクエリ言語であり、既存のデータに対するクエリを実行するためのランタイムです。RESTful APIと比較して、以下のような特徴があります:
- 必要なデータだけを取得可能: クライアントは必要なデータだけを指定して取得できるため、オーバーフェッチングを防ぎます。
- 一度のリクエストで複数リソースを取得: 複数のエンドポイントへのリクエストが不要になります。
- 強力な型システム: スキーマで型が定義されるため、型安全性が高まります。
- 自己文書化API: スキーマがそのままドキュメントとなります。
- 進化するAPI: 後方互換性を保ちながらAPIを発展させやすくなります。
開発環境のセットアップ
必要なもの
- PHP 8.0以上
- Composer
- Laravel 9以上
- GraphQLパッケージ(Lighthouse)
Laravelプロジェクトの作成
まずはLaravelプロジェクトを作成します。
composer create-project laravel/laravel laravel-graphql-api
cd laravel-graphql-api
Lighthouseのインストール
LaravelでGraphQLを実装するために、Lighthouseというパッケージを使用します。Lighthouseは、スキーマファースト開発を実現するLaravel用のGraphQLパッケージです。
composer require nuwave/lighthouse
設定ファイルの公開
Lighthouseの設定ファイルをプロジェクトに公開します。
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=config
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema
これにより、config/lighthouse.php
とgraphql/schema.graphql
ファイルが作成されます。
データベースの設定
マイグレーションとモデルの作成
例として、ブログポストとそのカテゴリを管理するAPIを作成します。まずはマイグレーションとモデルを作成します。
php artisan make:model Category -m
php artisan make:model Post -m
database/migrations/xxxx_xx_xx_create_categories_table.php
を編集します:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('categories');
}
};
database/migrations/xxxx_xx_xx_create_posts_table.php
を編集します:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->boolean('published')->default(false);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('posts');
}
};
モデルファイルを編集して、リレーションシップを定義します。
app/Models/Category.php
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = ['name', 'description'];
public function posts()
{
return $this->hasMany(Post::class);
}
}
app/Models/Post.php
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = ['category_id', 'title', 'content', 'published'];
public function category()
{
return $this->belongsTo(Category::class);
}
}
マイグレーションを実行します:
php artisan migrate
シードデータの作成(オプション)
開発をスムーズに進めるために、テストデータを作成することをおすすめします。
php artisan make:seeder CategorySeeder
php artisan make:seeder PostSeeder
database/seeders/CategorySeeder.php
:
<?php
namespace Database\Seeders;
use App\Models\Category;
use Illuminate\Database\Seeder;
class CategorySeeder extends Seeder
{
public function run()
{
Category::create([
'name' => 'Laravel',
'description' => 'Laravel関連の記事'
]);
Category::create([
'name' => 'PHP',
'description' => 'PHP関連の記事'
]);
Category::create([
'name' => 'GraphQL',
'description' => 'GraphQL関連の記事'
]);
}
}
database/seeders/PostSeeder.php
:
<?php
namespace Database\Seeders;
use App\Models\Post;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
public function run()
{
Post::create([
'category_id' => 1,
'title' => 'Laravelの基本',
'content' => 'Laravelは、PHPのモダンなWebアプリケーションフレームワークです。',
'published' => true
]);
Post::create([
'category_id' => 3,
'title' => 'GraphQLとは',
'content' => 'GraphQLは、APIのためのクエリ言語です。',
'published' => true
]);
Post::create([
'category_id' => 2,
'title' => 'PHP 8の新機能',
'content' => 'PHP 8では多くの新機能が追加されました。',
'published' => false
]);
}
}
database/seeders/DatabaseSeeder.php
を編集して、作成したシーダーを呼び出します:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call([
CategorySeeder::class,
PostSeeder::class,
]);
}
}
シードを実行します:
php artisan db:seed
GraphQLスキーマの定義
GraphQLでは、スキーマがAPIの構造を定義します。graphql/schema.graphql
ファイルを編集して、必要な型とクエリを定義します。
"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`."
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
type Query {
categories: [Category!]! @all
category(id: ID! @eq): Category @find
posts: [Post!]! @all
post(id: ID! @eq): Post @find
searchPosts(title: String @where(operator: "like")): [Post!]! @all
}
type Mutation {
createCategory(
name: String!
description: String
): Category! @create
updateCategory(
id: ID!
name: String
description: String
): Category! @update
deleteCategory(
id: ID!
): Category! @delete
createPost(
category_id: ID!
title: String!
content: String!
published: Boolean = false
): Post! @create
updatePost(
id: ID!
category_id: ID
title: String
content: String
published: Boolean
): Post! @update
deletePost(
id: ID!
): Post! @delete
}
type Category {
id: ID!
name: String!
description: String
posts: [Post!]! @hasMany
created_at: DateTime!
updated_at: DateTime!
}
type Post {
id: ID!
category: Category! @belongsTo
title: String!
content: String!
published: Boolean!
created_at: DateTime!
updated_at: DateTime!
}
上記のスキーマでは、以下のことを定義しています:
DateTime
スカラー型:日時を扱うためのカスタム型Query
型:データを取得するための操作- 全カテゴリ取得
- ID指定でカテゴリ取得
- 全投稿取得
- ID指定で投稿取得
- タイトルで投稿検索
Mutation
型:データを変更するための操作- カテゴリの作成・更新・削除
- 投稿の作成・更新・削除
Category
型:カテゴリのデータ構造Post
型:投稿のデータ構造
Lighthouseのディレクティブ(@all
、@find
、@create
など)を使用することで、リゾルバーを自動的に生成しています。
GraphQL Playgroundのセットアップ
GraphQLのクエリをブラウザから簡単にテストするために、GraphQL Playgroundをセットアップします。
composer require mll-lab/laravel-graphql-playground
config/app.php
のproviders
配列に以下を追加します:
MLL\GraphQLPlayground\GraphQLPlaygroundServiceProvider::class,
これで、/graphql-playground
にアクセスするとGraphQL Playgroundが表示されます。
APIのテスト
APIが正しく動作しているか確認するために、いくつかのクエリを試してみましょう。
カテゴリ一覧の取得
{
categories {
id
name
description
}
}
投稿一覧の取得(カテゴリ情報含む)
{
posts {
id
title
content
published
category {
id
name
}
}
}
新しいカテゴリの作成
mutation {
createCategory(
name: "JavaScript"
description: "JavaScript関連の記事"
) {
id
name
description
}
}
新しい投稿の作成
mutation {
createPost(
category_id: 4
title: "JavaScriptの基本"
content: "JavaScriptは、Web開発に欠かせない言語です。"
published: true
) {
id
title
category {
name
}
}
}
投稿の更新
mutation {
updatePost(
id: 4
title: "JavaScriptの基本と応用"
content: "JavaScriptは、Web開発に欠かせない言語です。応用範囲も広がっています。"
) {
id
title
content
}
}
タイトルによる投稿の検索
{
searchPosts(title: "%基本%") {
id
title
category {
name
}
}
}
認証と認可
実際のアプリケーションでは、APIアクセスに認証と認可が必要になることがほとんどです。Lighthouseでは、Laravelの認証システムと統合して、これらの機能を実装できます。
認証パッケージのインストール
composer require laravel/sanctum
マイグレーションを実行します:
php artisan migrate
スキーマに認証クエリを追加
graphql/schema.graphql
に認証関連のクエリを追加します:
extend type Mutation {
login(
email: String!
password: String!
): AuthPayload!
register(
name: String!
email: String!
password: String!
): AuthPayload!
logout: Boolean!
}
type AuthPayload {
access_token: String!
user: User!
}
type User {
id: ID!
name: String!
email: String!
}
認証リゾルバーの作成
認証関連のクエリのリゾルバーを作成します:
php artisan lighthouse:mutation Login
php artisan lighthouse:mutation Register
php artisan lighthouse:mutation Logout
app/GraphQL/Mutations/Login.php
の内容:
<?php
namespace App\GraphQL\Mutations;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class Login
{
public function __invoke($_, array $args)
{
$user = User::where('email', $args['email'])->first();
if (!$user || !Hash::check($args['password'], $user->password)) {
throw new \Exception('Invalid credentials');
}
return [
'access_token' => $user->createToken('api_token')->plainTextToken,
'user' => $user,
];
}
}
app/GraphQL/Mutations/Register.php
の内容:
<?php
namespace App\GraphQL\Mutations;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
class Register
{
public function __invoke($_, array $args)
{
$user = User::create([
'name' => $args['name'],
'email' => $args['email'],
'password' => Hash::make($args['password']),
]);
return [
'access_token' => $user->createToken('api_token')->plainTextToken,
'user' => $user,
];
}
}
app/GraphQL/Mutations/Logout.php
の内容:
<?php
namespace App\GraphQL\Mutations;
class Logout
{
public function __invoke($_, array $args, $context)
{
$user = $context->user();
if ($user) {
$user->tokens()->delete();
return true;
}
return false;
}
}
スキーマに認証ガードを追加
一部のクエリやミューテーションに認証を要求するために、@guard
ディレクティブを使用します。graphql/schema.graphql
を編集します:
type Mutation {
# 既存のミューテーション...
# 認証が必要なミューテーション
createPost(
category_id: ID!
title: String!
content: String!
published: Boolean = false
): Post! @create @guard
updatePost(
id: ID!
category_id: ID
title: String
content: String
published: Boolean
): Post! @update @guard
deletePost(
id: ID!
): Post! @delete @guard
}
これにより、createPost
、updatePost
、deletePost
ミューテーションは認証されたユーザーのみが実行できるようになります。
バリデーション
GraphQLのミューテーションにバリデーションを追加することも重要です。Lighthouseでは、@rules
ディレクティブを使用してバリデーションを定義できます。
graphql/schema.graphql
を編集して、バリデーションルールを追加します:
type Mutation {
createCategory(
name: String! @rules(apply: ["required", "string", "max:255", "unique:categories,name"])
description: String
): Category! @create
updateCategory(
id: ID!
name: String @rules(apply: ["string", "max:255", "unique:categories,name"])
description: String
): Category! @update
createPost(
category_id: ID! @rules(apply: ["required", "exists:categories,id"])
title: String! @rules(apply: ["required", "string", "max:255"])
content: String! @rules(apply: ["required", "string"])
published: Boolean = false
): Post! @create @guard
updatePost(
id: ID!
category_id: ID @rules(apply: ["exists:categories,id"])
title: String @rules(apply: ["string", "max:255"])
content: String @rules(apply: ["string"])
published: Boolean
): Post! @update @guard
}
エラーハンドリング
GraphQLエラーを適切に処理するために、Lighthouseのエラーハンドリング機能を使用します。config/lighthouse.php
ファイルでエラーハンドラーを設定できます。
デフォルトでは、GraphQLエラーはJSONレスポンスのerrors
配列に含まれます。例えば、バリデーションエラーの場合:
{
"errors": [
{
"message": "Validation failed for the field [createCategory].",
"extensions": {
"validation": {
"name": [
"The name has already been taken."
]
},
"category": "validation"
}
}
],
"data": {
"createCategory": null
}
}
クライアントサイドの実装
GraphQL APIをフロントエンドから使用するには、さまざまなクライアントライブラリがあります。例えば、React用のApollo Client
やurql
、Vue.js用のvue-apollo
などがあります。
簡単な例として、fetch
を使用してGraphQLクエリを実行する方法を示します:
async function fetchGraphQL(query, variables = {}) {
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
// 認証が必要な場合
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify({
query,
variables,
}),
});
return response.json();
}
// 使用例:カテゴリ一覧を取得
fetchGraphQL(`
query {
categories {
id
name
description
}
}
`).then(result => {
console.log(result.data.categories);
});
// 使用例:ログイン
fetchGraphQL(`
mutation($email: String!, $password: String!) {
login(email: $email, password: $password) {
access_token
user {
id
name
email
}
}
}
`, {
email: 'user@example.com',
password: 'password'
}).then(result => {
const token = result.data.login.access_token;
localStorage.setItem('token', token);
});
パフォーマンス最適化
GraphQLでは、N+1クエリ問題が発生する可能性があります。例えば、複数の投稿を取得し、それぞれのカテゴリ情報も取得する場合、データベースクエリが増加します。
Lighthouseでは、この問題を解決するために@with
ディレクティブを使用できます:
type Query {
posts: [Post!]! @all @with(relation: "category")
}
また、キャッシュを使用してパフォーマンスを向上させることもできます。config/lighthouse.php
でキャッシュ設定を行います:
'cache' => [
'enable' => env('LIGHTHOUSE_CACHE_ENABLE', true),
'ttl' => env('LIGHTHOUSE_CACHE_TTL', 60),
],
運用環境での注意点
運用環境でGraphQL APIをデプロイする際には、以下の点に注意しましょう:
- Playgroundの無効化: 本番環境ではGraphQL Playgroundを無効にすることを検討してください。
- クエリの複雑さの制限: 非常に複雑なクエリはサーバーに負荷をかける可能性があります。クエリの複雑さを制限するミドルウェアを導入することを検討してください。
- レート制限: APIの乱用を防ぐために、レート制限を実装することをおすすめします。
- モニタリング: APIのパフォーマンスを監視し、問題があれば迅速に対応できるようにしましょう。
まとめ
この記事では、LaravelとGraphQLを使用したAPI開発の基本を解説しました。GraphQLの柔軟性と効率性は、特に複雑なデータ構造や多様なクライアントの要件に対応する必要がある場合に大きなメリットとなります。
Lighthouseパッケージを使用することで、LaravelでGraphQL APIを簡単に構築できます。スキーマファースト開発アプローチにより、APIの設計と実装が明確になり、フロントエンドとバックエンドの連携もスムーズに行えます。
GraphQLは比較的新しい技術ですが、その利点から多くのプロジェクトで採用されています。ぜひLaravelアプリケーションでGraphQLを試してみてください。
コメント