0

I've got an installation of Laravel Lighthouse and building up my API endpoint. I have some endpoints where I'd like to permit only some fields to be returned depending on the user's permission levels.

Say, for example, I want to remove the email field from the response entirely. I have already implemented my own ScopedField directive as below:

<?php

namespace App\GraphQL\Directives;

use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

final class ScopedFieldDirective extends BaseDirective implements FieldMiddleware
{
    public static function definition(): string
    {
        return /** @lang GraphQL */ <<<'GRAPHQL'
directive @scopedField(
  """
  The ability to check permissions for.
  """
  ability: [String]! = []
  
  """
  The default value to return when ability is invalid
  """
  default: String = null

) on FIELD_DEFINITION
GRAPHQL;
    }
    
    public function handleField(FieldValue $fieldValue, \Closure $next)
    {
        $resolver = $fieldValue->getResolver();
        
        $fieldValue->setResolver(function (
            $root,
            array $args,
            GraphQLContext $context,
            ResolveInfo $info
        ) use (
            $resolver
        ) {
            $resolve = fn() => $resolver($root, $args, $context, $info);
            $default = fn() => $this->directiveArgValue('default', null);
            
            if (!$root instanceof Model) {
                return $resolve();
            }
            
            if (
                ($abilities = $this->directiveArgValue('ability'))
                && !Gate::check($abilities, $root)
            ) {
                // null out our value
                return $default();
            }
            
            return $resolve();
        });
        
        return $next($fieldValue);
    }
}

Here's an example schema:

type User {
    id: ID!
    
    first_name: String!
    
    email: String @scopedField(ability: ["read"])
}

This directive means that on a per-field basis, I can intercept and return a default value, e.g. null. But what if I wanted to remove this field from the response altogether? Is this possible / is it even recommended - or should I just leave the field as potentially nullable?

Thanks, Chris.

Chris
  • 1,939
  • 1
  • 19
  • 38

1 Answers1

1

GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.

The whole concept of graphQL is to avoid over-fetching and under-fetching. Giving null or 'unauthenticated' to the client is acceptable.

If you must remove it, you can use the light-house manipulate result event. You will hook into the lifecycle of the request, get the result as a collection, and remove what you want.

mostafa
  • 196
  • 4
  • Thanks for your input Mostafa. Completely agree - perhaps I just throw an error if the user is unauthorised to access that field. That'd solve the issue and provide good feedback. Appreciate your assistance – Chris Nov 18 '22 at 09:37