Search code examples
phplaravellaravel-routing

LARAVEL - Unable to use destroy route when generating it with Route::resource


CONTEXT. I am working in Laravel 8 and I use the DataTables plugin to display the records from a database. As you will see in the code, I create all the routes to do CRUD using: Route::resource('animal', 'AnimalController'). This generates a path for the DESTROY method like this: animal/{id}

PROBLEM. When I press the delete button for a specific record, the record is deleted, fine, but the browser console returns this error: "The DELETE method is not supported for this route. Supported methods: GET, HEAD, POST", and therefore Javascript is broken and the alert that the record was deleted never show. And if I change the request type to POST instead of DELETE, the error tells me that POST is not supported and DELETE is!

QUESTION: what am I doing wrong?

ROUTE

Route::resource('animal', 'AnimalController');

CONTROLLER to print the DataTable

public function index(Request $request)
{
    if($request->ajax()) {
        $animals = DB::select('CALL sp_select_animal()');
        return DataTables::of($animals)
            ->addIndexColumn()
            ->addColumn('action', function($animals){
                $actions  = '<a href="" class="btn btn-info btn-sm">Edit</a>';
                $actions .= '<button type="button" id="'.$animals->id.'" data-name="'.$animals->name.'" name="delete" class="btn btn-danger btn-sm ml-1 delete">Delete</button>';
                return $actions;
            })
            ->rawColumns(['action'])
            ->make(true);
    }
    return view('animal.index');
}

CONTROLLER to delete record

public function destroy($id)
{
    $animal = DB::select('CALL sp_destroy_animal(?)', [$id]);
    return back();
}

VIEW

jQuery('#BTNdelete').click(function(e){
    e.preventDefault();
    var url = '{{ route("animal.destroy", ":xxx") }}';
    url = url.replace(':xxx', animalID);
    jQuery.ajax({
        "url": url,
        "type": 'DELETE',
        "data": {
            "_token": "{{ csrf_token() }}"
        },
        success: function(data) {
            setTimeout(function() {
                jQuery('#modal').modal('hide');
                toastr.warning('The record was successfully deleted', 'Delete Record', {timeOut:3000});
                jQuery('#tableAnimal').DataTable().ajax.reload();
            }, 100);
        }
    });
});

Now everything works fine if I create a specific route to delete records, for example

Route::get('animal/delete/{id}', [AnimalController::class, 'destroy'])->name('animal.destroy')

and this would fix the problem. But I don't want to create an extra route. I want to use the routes generated by using the resource() method.


Solution

  • Lots of problems in the JavaScript, but the root of your problem is that browsers can't make DELETE requests, so they have to fake them.

    // bind to the class of the button, not a non-existent ID
    jQuery('button.delete').click(function(e) {
        // this isn't really needed, a button has no default action
        e.preventDefault();
        // you do want to stop the event from bubbling though
        e.stopPropagation();
        // this properly escapes the value for JavaScript, not HTML
        var url = @json(route("animal.destroy", ":xxx"));
        // WHAT IS animalID ???
        url = url.replace(':xxx', animalID);
        // build the post request with the fake method added
        jQuery.post(
            url,
            {_token: @json(csrf_token()), _method: "DELETE"},
            function(data) {
                setTimeout(function() {
                    jQuery('#modal').modal('hide');
                    toastr.warning('The record was successfully deleted', 'Delete Record', {timeOut:3000});
                    jQuery('#tableAnimal').DataTable().ajax.reload();
                }, 100);
            }
        );
    });
    

    But it must be said, your controller's index() method should NOT be dealing with HTML; that is exclusively the job of the view. Provide it only with the data it needs to build the HTML. This can be done using column.render. See this question for an example.