Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

devionti's avatar

RESTful API Many to Many

I have many models that have a many to many relation, so usually there is a pivot table. Lets use Roles and permission as an example. These are the tables:

  • Roles
  • Permissions
  • Role_has_Permissions
  • model_has roles
  • model_has_permissions

model_has_roles only has role_id and permission_id

Since the pivot table has two primary keys, how would I show, post, update, detach(delete).

Like for the roles table it has ID I can do CRUD in restful.

0 likes
8 replies
SarwarAhmed's avatar

@devionti You need just three tables.

  • roles
  • permissions
  • permissions_roles
// Role Model
public function permissions(){
	return $this->belongsToMany(Permission::class);
}

// Permission Model
public function roles(){
	return $this->belongsToMany(Role::class);
}

// uses
$roles = Permission::find(1)->roles;
devionti's avatar

the third table is role_has_permission roles who have permissions attached

model_has_permission and model_has_role is for users who have permission and users who have roles. Sometimes users have permissions that are not in the role. E.g. a role that is used by multiple people but there is this extra permission attached to the user.

I already have that in model, but my question is more about the API controller

E.g. For normal Roles this is the api controller

  public function index()
      {
          return Roles::all();
      }

      public function store(Request $request)
      {
          return Roles::create($request->all());
      } 

  public function update(Request $request, Roles $id)
      {
          $role = Roles::findOrFail($id);
          $role->update($request->all());
  
          return $role;
      }

  public function delete(Roles $id)
      {
          $role = Roles::findOrFail($id);
          $role->delete();
  
          return 204;
      }

For Request {{URL_API}}/api/roles/{role_id}

So my question is how would I create an API controller for the many to many Like the function

I think the request could be something like this {{URL_API}}/api/roles/{role_id}/permissions

I am just unsure about the post, get, update, delete implementation for the pivot table.

thewebartisan7's avatar

I think he is referring to database structure of spatie permissions, which has the same db structure.

About your question, you don't need to show, post or delete the pivot table, but in that specific case, better call it grant/revoke.

Or if you prefer you need only create/delete.

You can do something like this:

// Grant role_id the permission_id
PATCH /roles/role_id/permission/permission_id
// Revoke role_id the $permissionId
DELETE /roles/role_id/permission/permission_id 

And for user like:

// Grant user_id the permission_id
PATCH /users/user_id/permission/permission_id
// Revoke user_id the permission_id
DELETE /users/user_id/permission/permission_id 

For display all permissions of a role or of a user is ok like you write:

{{URL_API}}/api/roles/{role_id}/permissions

{{URL_API}}/api/users/{user_id}/permissions

I create one single AuthorizationController for grant/revoke users and roles, so that part has a separated policies and logic is in one controller.

The url become a bit more longer as I use something like /authorizations/user/user_id/permission/permission_id and same for role.

Then I have one page for role and one for user, with matrix of permission, see role https://ibb.co/JrWbSrZ and here user https://ibb.co/n68Nybn

automica's avatar

@devionti you can change your verbs depending on action.

  • GET - gets the record
  • POST - creates a new one
  • PATCH - updates an existing one
  • DELETE - deletes an existing record

all against the same endpoint

devionti's avatar

Naming asside would creating a model for the pivot table work or good practice? How would I get the pivot table would I need to create a model for the pivot table RoleHasPermissions Model Model

<?php

namespace App\Models\Authorization;

use Illuminate\Database\Eloquent\Model;

class RoleHasPermissions extends Model
{
    protected $table = 'role_has_permissions';
    protected $primaryKey = 'role_id';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'role_id',
        'permission_id',
    ];
}

API Get Implementation

  public function get()
      {
          return RoleHasPermissions::all();
      }

 public function create(Request $request)
      {
          return RoleHasPermissions::create($request->all());
      } 

  public function patch(Request $request, RoleHasPermissions $id)
      {
          $roleHasPermissions = RoleHasPermissions::findOrFail($id);
          $roleHasPermissions->update($request->all());
  
          return $roleHasPermissions;
      }

 public function delete(RoleHasPermissions $id)
      {
          $roleHasPermissions = RoleHasPermissions::findOrFail($id);
          $roleHasPermissions->delete();
  
          return 204;
      }

thewebartisan7's avatar

No, you don't need to create a model for pivot table.

If you are using Spatie permission then it should be something like this:

    public function grantRole(Role $role, Permission $permission)
    {
        $role->givePermissionTo($permission);
    }

And revoke permission:

    public function grantRole(Role $role, Permission $permission)
    {
        $role->revokePermissionTo($permission);
    }

And similar for users:

    public function grantUser(Permission $permission, User $user)
    {
        $user->givePermissionTo($permission);
    }

Revoke:

    public function revokeUser(Permission $permission, User $user)
    {
        $user->revokePermissionTo($permission);
    }
devionti's avatar

I am using spacie permission but I mentioned it specifically as an example. I do have other many to many relationship tables as well in other modules.

E.g. user belongs to many Helpdesk Requests Table is User_Helpdesk, So do I need to try to make a similar implementation to givePermissionTo

public function givePermissionTo(...$permissions)
    {
        $permissions = collect($permissions)
            ->flatten()
            ->map(function ($permission) {
                if (empty($permission)) {
                    return false;
                }

                return $this->getStoredPermission($permission);
            })
            ->filter(function ($permission) {
                return $permission instanceof Permission;
            })
            ->each(function ($permission) {
                $this->ensureModelSharesGuard($permission);
            })
            ->map->id
            ->all();

        $model = $this->getModel();

        if ($model->exists) {
            $this->permissions()->sync($permissions, false);
            $model->load('permissions');
        } else {
            $class = \get_class($model);

            $class::saved(
                function ($object) use ($permissions, $model) {
                    static $modelLastFiredOn;
                    if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) {
                        return;
                    }
                    $object->permissions()->sync($permissions, false);
                    $object->load('permissions');
                    $modelLastFiredOn = $object;
                }
            );
        }

        $this->forgetCachedPermissions();

        return $this;
    }

Please or to participate in this conversation.