laraveljson-apilaravel-api

How can I create a belongsToMany relationship with Laravel JSON:API?


I am familiar with creating a Many To Many relationship using Laravel.

In my example, I have many resources that share many activities. I have been able to successfully create this relationship in their respective models:

// Activity.php

public function resources(): BelongsToMany
{
    return $this->belongsToMany(Resource::class)->withTimestamps();
}
// Resource.php

public function activities(): BelongsToMany
{
    return $this->belongsToMany(Activity::class)->withTimestamps()
            ->using(new class extends Pivot {
                use HasUuids;
            });
}

I'll then use the sync method to create the relationship.

$Ids = ['9a4cf36f-7683-4656-9653-9a01e1edbca1', '9a5225f0-31f6-40be-8863-6e8de61ef29f'];
$resource->activities()->sync($activityIds);

I'm attempting to create this same relationship using the Laravel JSON:API package.

My ActivitySchema looks like this:

return [
    ...
    BelongsToMany::make('resources')->readOnly(),
    ...

My ResourceSchema looks like this:

return [
    ...
    ArrayList::make('activities'),
    
    ...

    BelongsToMany::make('activities')->readOnly(),
    ...

Using Postman, when creating or updating a resource, my request body looks like this:

"attributes": {
    "activities": ["9a4cf36f-7683-4656-9653-9a01e1edbca1", "9a5225f0-31f6-40be-8863-6e8de61ef29f"]
    ...

The error I'm currently getting when creating a new resource is:

"detail": "The field activities is not a supported attribute.",

Usually that means I am missing the field on my Schema. I have verified that it's there, so I'm wondering if it's due to the relationship?

I have tried using a pivot model, but no success there either. Do I need to create a custom controller to handle this logic? I'm not having much luck finding any instructions on how to sync the models.

How can I create/sync the relationship using the JSON:API package?


Solution

  • You will want to check out the tutorial as it has an example of a many to many relationship; Posts -- Tags.

    In addition to the "attributes" you want to update in your resource, you'll also need to include the relationships.

    For example, your resource PATCH request body will look something like this:

    {
        "data": {
            "type": "resources",
            "id": "676110ee-c302-4239-8eac-1f9e87ebaae8",
            "attributes": {
                "some_id": "01b77de1-d524-4794-af90-88f64dd68999",
                "name": "My Resource",
                "description": "This is some updated text!"
            },
            "relationships": {
                "activities": {
                    "data": [
                        {
                            "type": "activities",
                            "id": "9a4cf36f-7683-4656-9653-9a01e1edbca1"
                        },
                        {
                            "type": "activities",
                            "id": "9a5225f0-31f6-40be-8863-6e8de61ef29f"
                        }
                    ]
                }
            }   
        }
    }
    

    Now in the response that is sent back you'll see the relationship(s).

    In addition to the above, you'll also need to update the resource request to allow for the toMany rule:

    // ResourceRequest.php
    
    
    public function rules(): array
    {
        return [
            ...
    
            'name' => 'required|string|max:255',
            'description' =>  'required',
            'activities' => JsonApiRule::toMany(),
        ];
    }
    

    If you follow the relationship, you should now see the data returned correctly.

    "relationships": {
        "activities": {
            "links": {
                "related": "http://my.api.test/api/v1/resources/9a528648-37e9-4f58-b37c-f4e3a6cb478d/activities",
                "self": "http://my.api.test/api/v1/resources/9a528648-37e9-4f58-b37c-f4e3a6cb478d/relationships/activities"
            }
        }
    }
    

    If you pass in the included query, the api will send back the requested relationship in the response.

    /api/v1/resources/:resource_id?include=activities
    
    ...
    
    "included": [
        {
            "type": "activities",
            "id": "9a4cf36f-7683-4656-9653-9a01e1edbca1",
            "attributes": {...}