DELETE to Remove Data [API Platform]


We can now GET, POST, and PUT when working with our API Platform Album resources. It would be super nice to round off with a DELETE implementation. So let's do that, shall we?

As we've covered in all of the previous videos in this API Platform tutorial, mostly the hard work has already been done for us. If we weren't customising our Operations slightly, there's very little we'd need to do after simply defining our entity / data model.

Let's start by looking at our existing Behat test:

Feature: Provide a consistent standard JSON API endpoint

  In order to build interchangeable front ends
  As a JSON API developer
  I need to allow Create, Read, Update, and Delete functionality

  Background:
    Given there are Albums with the following details:
      | title                              | track_count | release_date              |
      | some fake album name               | 12          | 2020-01-08T00:00:00+00:00 |
      | another great album                | 9           | 2019-01-07T23:22:21+00:00 |
      | now that's what I call Album vol 2 | 23          | 2018-02-06T11:10:09+00:00 |
    And the "Content-Type" request header is "application/json"

  Scenario: Can delete an Album
    Given I request "/album/3" using HTTP GET
    Then the response code is 200
    When I request "/album/3" using HTTP DELETE
    Then the response code is 204
    When I request "/album/3" using HTTP GET
    Then the response code is 404

The DELETE test is a little unusual.

Because we have no direct access to the database we instead rely on consuming the API directly to ensure:

  • The resource exists
  • We can delete the resource
  • The resource no longer exists

If you have been following along with the previous videos you will know also that as we haven't explicitly defined our DELETE item operation, yet we have defined some custom item operations, that the DELETE route will not exist.

We need to add in another annotation. It's very easy - pretty much just copy / paste what we used for GET and PUT:

<?php

// api/src/Entity/Album.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ApiResource(
 *      collectionOperations = {
 *          "get"={
 *              "method"="GET",
 *              "path"="/album.{_format}",
 *          },
 *          "post"={
 *              "method"="POST",
 *              "path"="/album.{_format}",
 *          },
 *      },
 *     itemOperations={
 *          "get"={
 *              "method"="GET",
 *              "path"="/album/{id}.{_format}",
 *          },
+*          "delete"={
+*              "method"="DELETE",
+*              "path"="/album/{id}.{_format}",
+*          },
 *          "put"={
 *              "method"="PUT",
 *              "path"="/album/{id}.{_format}",
 *          },
 *     }
 * )
 * @ORM\Entity()
 * @ApiResource(iri="http://schema.org/MusicAlbum")
 */
class Album
{

Which gives us the expected route:

 docker-compose exec php php bin/console debug:router
 ---------------------------- -------- -------- ------ --------------------------------- 
  Name                         Method   Scheme   Host   Path                             
 ---------------------------- -------- -------- ------ --------------------------------- 
  api_albums_get_collection    GET      ANY      ANY    /album.{_format}                 
  api_albums_post_collection   POST     ANY      ANY    /album.{_format}                 
  api_albums_get_item          GET      ANY      ANY    /album/{id}.{_format}            
  api_albums_delete_item       DELETE   ANY      ANY    /album/{id}.{_format}            
  api_albums_put_item          PUT      ANY      ANY    /album/{id}.{_format}            
 ---------------------------- -------- -------- ------ --------------------------------- 

And running the test:

vendor/bin/behat features/album.feature --tags=t

Feature: Provide a consistent standard JSON API endpoint

  In order to build interchangeable front ends
  As a JSON API developer
  I need to allow Create, Read, Update, and Delete functionality

  Background:                                                   # features/album.feature:7
    Given there are Albums with the following details:          # FeatureContext::thereAreAlbumsWithTheFollowingDetails()
      | title                              | track_count | release_date              |
      | some fake album name               | 12          | 2020-01-08T00:00:00+00:00 |
      | another great album                | 9           | 2019-01-07T23:22:21+00:00 |
      | now that's what I call Album vol 2 | 23          | 2018-02-06T11:10:09+00:00 |
    And the "Content-Type" request header is "application/json" # Imbo\BehatApiExtension\Context\ApiContext::setRequestHeader()

  @t
  Scenario: Can delete an Album                 # features/album.feature:43
    Given I request "/album/3" using HTTP GET   # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    When I request "/album/3" using HTTP DELETE # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 204               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    When I request "/album/3" using HTTP GET    # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 404               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()

1 scenario (1 passed)
8 steps (8 passed)
0m0.19s (9.82Mb)

Wow, the easiest one yet.

All we have left to do are the error checks.

Episodes

# Title Duration
1 What will our JSON API actually do? 08:46
2 What needs to be in our Database for our Tests to work? 12:32
3 Cleaning up after each Test Run 02:40
4 Docker makes for Easy Databases 09:01
5 Healthcheck [Raw Symfony 4] 07:53
6 Send in JSON data using POST [Raw Symfony 4] 05:33
7 Keep your data nice and tidy using Symfony's Form [Raw Symfony 4] 10:48
8 Validating incoming JSON [Raw Symfony 4] 08:26
9 Nicer error messages [Raw Symfony 4] 06:23
10 GET'ting data from our Symfony 4 API [Raw Symfony 4] 08:11
11 GET'ting a collection of Albums [Raw Symfony 4] 01:50
12 Update existing Albums with PUT [Raw Symfony 4] 05:00
13 Upsetting Purists with PATCH [Raw Symfony 4] 02:39
14 Hitting DELETE [Raw Symfony 4] 02:11
15 How to open your API to the outside world with CORS [Raw Symfony 4] 07:48
16 Getting Setup with Symfony 4 and FOSRESTBundle [FOSRESTBundle] 09:11
17 Healthcheck [FOSRESTBundle] 06:14
18 Handling POST requests [FOSRESTBundle] 08:31
19 Saving POST data to the database [FOSRESTBundle] 09:44
20 Work with XML, or JSON, or Both [FOSRESTBundle] 04:31
21 Going far, then Too Far with the ViewResponseListener [FOSRESTBundle] 03:19
22 GET'ting data from your Symfony 4 API [FOSRESTBundle] 05:58
23 GET'ting a Collection of data from your Symfony 4 API [FOSRESTBundle] 01:27
24 Updating with PUT [FOSRESTBundle] 02:58
25 Partially Updating with PATCH [FOSRESTBundle] 02:15
26 DELETE'ing Albums [FOSRESTBundle] 01:27
27 Handling Errors [FOSRESTBundle] 08:58
28 Introducing the API Platform [API Platform] 08:19
29 The Entry Point [API Platform] 04:30
30 The Context [API Platform] 05:52
31 Healthcheck - Custom Endpoint [API Platform] 05:17
32 Starting with POST [API Platform] 07:08
33 Creating Entities with the Schema Generator [API Platform] 07:38
34 Defining A Custom POST Route [API Platform] 07:31
35 Finishing POST [API Platform] 06:29
36 GET'ting One Resource [API Platform] 02:50
37 GET'ting Multiple Resources [API Platform] 02:59
38 PUT to Update Existing Data [API Platform] 02:19
39 DELETE to Remove Data [API Platform] 01:15
40 No One Likes Errors [API Platform] 03:28