From d11d2b7acc79441413b28b179913e1b588738d57 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 5 Jun 2026 11:41:05 +0200 Subject: [PATCH] fix(laravel): register graphql routes before catch-all entrypoint When api-platform.defaults.route_prefix is set to an empty string, the catch-all entrypoint route /{index?}{_format?} is registered before /graphql and /graphiql. Laravel matches routes in registration order, so GET requests to /graphql are captured by the entrypoint controller and fail with `Format "raphql" is not supported` (the leading `g` is stripped as the optional `index` parameter). Moving the GraphQL/GraphiQL route registrations ahead of the entrypoint group fixes the match regardless of the prefix value. With a non-empty prefix (e.g. /api) there is no overlap, so behavior is unchanged. Fixes #8128 --- src/Laravel/Tests/GraphQlRoutePrefixTest.php | 64 ++++++++++++++++++++ src/Laravel/routes/api.php | 38 ++++++------ 2 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 src/Laravel/Tests/GraphQlRoutePrefixTest.php diff --git a/src/Laravel/Tests/GraphQlRoutePrefixTest.php b/src/Laravel/Tests/GraphQlRoutePrefixTest.php new file mode 100644 index 0000000000..15b2fc3d5e --- /dev/null +++ b/src/Laravel/Tests/GraphQlRoutePrefixTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Laravel\Tests; + +use ApiPlatform\Laravel\Test\ApiTestAssertionsTrait; +use Illuminate\Config\Repository; +use Illuminate\Foundation\Application; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Orchestra\Testbench\Concerns\WithWorkbench; +use Orchestra\Testbench\TestCase; + +class GraphQlRoutePrefixTest extends TestCase +{ + use ApiTestAssertionsTrait; + use RefreshDatabase; + use WithWorkbench; + + /** + * @param Application $app + */ + protected function defineEnvironment($app): void + { + tap($app['config'], static function (Repository $config): void { + $config->set('app.debug', true); + $config->set('api-platform.graphql.enabled', true); + $config->set('api-platform.defaults.route_prefix', ''); + }); + } + + public function testPostGraphQlWithEmptyRoutePrefix(): void + { + $response = $this->postJson('/graphql', ['query' => '{books { edges { node { id }}}}'], ['accept' => ['application/json']]); + $response->assertStatus(200); + $data = $response->json(); + $this->assertArrayHasKey('data', $data); + $this->assertArrayNotHasKey('errors', $data); + } + + public function testGetGraphQlWithEmptyRoutePrefix(): void + { + $response = $this->get('/graphql?query='.urlencode('{books { edges { node { id }}}}'), ['accept' => ['application/json']]); + $response->assertStatus(200); + $data = $response->json(); + $this->assertArrayHasKey('data', $data); + $this->assertArrayNotHasKey('errors', $data); + } + + public function testGetGraphiQlWithEmptyRoutePrefix(): void + { + $response = $this->get('/graphiql'); + $response->assertStatus(200); + } +} diff --git a/src/Laravel/routes/api.php b/src/Laravel/routes/api.php index 81e4045ea5..72d337efcf 100644 --- a/src/Laravel/routes/api.php +++ b/src/Laravel/routes/api.php @@ -79,6 +79,25 @@ $prefix = config()->get('api-platform.defaults.route_prefix', ''); Route::group(['prefix' => $prefix], static function (): void { + if (config()->get('api-platform.graphql.enabled')) { + Route::group([ + 'middleware' => config()->get('api-platform.graphql.middleware', []), + ], static function (): void { + Route::addRoute(['POST', 'GET'], '/graphql', GraphQlEntrypointController::class) + ->name('api_graphql'); + }); + + if (config()->get('api-platform.graphiql.enabled', true)) { + Route::group([ + 'middleware' => config()->get('api-platform.graphiql.middleware', []), + 'domain' => config()->get('api-platform.graphiql.domain', ''), + ], static function (): void { + Route::get('/graphiql', GraphiQlController::class) + ->name('api_graphiql'); + }); + } + } + Route::group(['middleware' => ApiPlatformMiddleware::class], static function (): void { Route::get('/contexts/{shortName?}{_format?}', static function (Request $request, ContextAction $contextAction, string $shortName = 'Entrypoint') { return $contextAction($shortName, $request); @@ -98,25 +117,6 @@ ->where('index', 'index') ->name('api_entrypoint'); }); - - if (config()->get('api-platform.graphql.enabled')) { - Route::group([ - 'middleware' => config()->get('api-platform.graphql.middleware', []), - ], static function (): void { - Route::addRoute(['POST', 'GET'], '/graphql', GraphQlEntrypointController::class) - ->name('api_graphql'); - }); - - if (config()->get('api-platform.graphiql.enabled', true)) { - Route::group([ - 'middleware' => config()->get('api-platform.graphiql.middleware', []), - 'domain' => config()->get('api-platform.graphiql.domain', ''), - ], static function (): void { - Route::get('/graphiql', GraphiQlController::class) - ->name('api_graphiql'); - }); - } - } }); });