1

I have a simple website running Laravel Jetstream with Teams enabled. On this website, you can create different "to-do tasks", which are owned by a team. I have a model called Task.

I am trying to create a public facing API, so my users can query their tasks from their own applications. In my routes/api.php file, I have added this:

Route::middleware('auth:sanctum')->group(function(){
    Route::apiResources([
        'tasks' => \App\Http\Controllers\API\TaskController::class,
    ]);
});

And then in the TaskController, I have only begun coding the index method:

/**
 * Display a listing of the resource.
 * @queryParam team int The team to pull tasks for.
 * @return \Illuminate\Http\Response
*/
public function index()
{

   if(request()->team){
        $tasks = Task::where('team_id', request()->team)->get();
        return TaskResource::collection($tasks);
   }

   return response([
        'status' => 'error',
        'description' => "Missing required parameter `team`."
   ], 422);
      
}

Now this works fine. I can make a GET request to https://example.org/api/tasks?team=1 and successfully get all the tasks related to team.id = 1.

However, what if I want to include multiple query parameters - some required, others only optional. For example, if I want to let users access all tasks with a given status:

https://example.org/api/tasks?team=1&status=0

What is the best practices around this? As I can see where things are going now, I will end up with a lot of if/else statement just to check for valid parameters and given a correct error response code if something is missing.

Edit

I have changed my URL to be: https://example.org/api/teams/{team}/tasks - so now the team must be added to the URL. However, I am not sure how to add filters with Spatie's Query Builder:

public function index(Team $team)
{

   $tasks = QueryBuilder::for($team)
            ->with('tasks')
            ->allowedFilters('tasks.status')
            ->toSql();
   dd($tasks);

}

So the above simply just prints:

"select * from `teams`"

How can I select the relationship tasks from team, with filters?

1 Answer 1

2

The right way

The advanced solution, i have built a handful of custom ways of handling search query parameters. Which is what you basically wants to do, the best solution by far is the spatie package Query Builder.

QueryBuilder::for(Task::class)
    ->allowedFilters(['status', 'team_id'])
    ->get();

This operation will do the same as you want to do, and you can filter it like so.

?fields[status]=1

In my experience making team_id searchable by team and similar cases is not worth it, just have it one to one between columns and input. The package has rich opportunities for special cases and customization.

The simple way

Something as straight forward like your problem, does not need a package off course. It is just convenient and avoids you writing some boiler plate code.

This is a fairly simple problem where you have a query parameter and a column you need to search in. This can be represented with an array where the $key being the query parameter and $value being the column.

$searchable = [
    'team' => 'team_id',
    'status' => 'status',
];

Instead of doing a bunch of if statements you can simplify it. Checking if the request has your $searchables and if act upon it.

$request = resolve(Request::class);
$query = Task::query();

foreach ($this->seachables as $key => $value) {
    if ($query->query->has($key)) {
        $query->where($value, $query->query->get($key))
    }
}

$tasks = $query->get();

This is a fairly basic example and here comes the problem not going with a package. You have to consider how to handle handle like queries, relationship queries etc.

In my experiences extending $value to an array or including closures to change the way the logic on the query builder works can be an option. This is thou the short coming of the simple solution.

Wrap up

Here you have two solutions where actually both are correct, find what suits your needs and see what you can use. This is a fairly hard problem to handle pragmatic, as the simply way often gets degraded as more an more explicit search functionality has to be implemented. On the other side using a package can be overkill, provide weird dependencies and also force you into a certain design approach. Pick your poison and hope at least this provides some guidance that can lead you in the right direction.

Sign up to request clarification or add additional context in comments.

4 Comments

What a detailed and very well put answer. Great explanations, drawbacks of the different solutions and well-written examples. This is exactly what I was looking for! Thank you!
I have made a small edit of my OP, as I have changed the API url a bit. Do you mind taking a quick look?
How did you send the query parameter for your example? it wont search unless
Doh! That was indeed the reason. I did it like this: ?status=1. Changed to ?filter[status]=1 and everything works. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.