Finishing POST [API Platform]


In this video we are continuing with our POST implementation. We're going to start by tackling the two problems we encountered towards the end of the previous video:

  • The releaseDate should not be null
  • We're expecting releaseDate, not release_date

The first of these two issues is with our current entity setup.

We either need to explicitly allow the releaseDate property to be nullable:

    /**
     * @var \DateTime|null
-    * @ORM\column(type="datetime")
+    * @ORM\column(type="datetime", nullable=true)
     */
    private $releaseDate;

Which is not what I want to do.

Or we need to add an assertion on to this property to throw a validation exception (a 400 error code), rather than trying to save to the database and failing (a 500 error code).

    /**
     * @var \DateTime|null
+    * @Assert\NotNull()
     * @ORM\column(type="datetime", nullable=true)
     */
    private $releaseDate;

Ok, now if we send in our 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 add a new Album             # features/album.feature:30
    Given the request body is:              # Imbo\BehatApiExtension\Context\ApiContext::setRequestBody()
      """
      {
        "title": "Awesome new Album",
        "track_count": 7,
        "release_date": "2030-12-05T01:02:03+00:00"
      }
      """
    When I request "/album" using HTTP POST # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 201           # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
      Expected response code 201, got 400. (Imbo\BehatApiExtension\Exception\AssertionFailedException)

--- Failed scenarios:

    features/album.feature:30

1 scenario (1 failed)
5 steps (4 passed, 1 failed)
0m0.11s (9.96Mb)

Great Success!

We switched out our 500 for a 400. It's not great, but the error message itself is much nicer:

{
    "type": "https://tools.ietf.org/html/rfc2616#section-10",
    "title": "An error occurred",
    "detail": "releaseDate: This value should not be null.",
    "violations": [
        {
            "propertyPath": "releaseDate",
            "message": "This value should not be null."
        }
    ]
}

If you've been following along with either the Symfony 4 JSON API, or Symfony 4 FOSRESTBundle API, you will likely be thinking... wow that's a nicer looking error message :) I agree.

Ok, but 400 or 500, it's still not a 201, which is what we want.

What The null?

Looking at the violation we can see that releaseDate is null. And it should not be null.

Well, releaseDate is null because we're using release_date.

In order to fix this, just follow the docs:

# api/config/services.yaml

services:
    # ... other stuff ...

    Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter: ~

(Watch the video for a little more on this.)

And also:

# api/config/packages/api_platform.yaml

api_platform:
    # ... other stuff ...
    name_converter: 'Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter'

Honestly there's not a whole lot to say about this process.

It's a piece of functionality provided by Symfony's Serializer. You can use this independently of API Platform.

There's an additional piece of documentation about this here.

You can also implement your own name_convertor by writing your own class that implements Symfony\Component\Serializer\NameConverter\NameConverterInterface, then using that instead.

Ok cool, and now we have a passing 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 add a new Album             # features/album.feature:30
    Given the request body is:              # Imbo\BehatApiExtension\Context\ApiContext::setRequestBody()
      """
      {
        "title": "Awesome new Album",
        "track_count": 7,
        "release_date": "2030-12-05T01:02:03+00:00"
      }
      """
    When I request "/album" using HTTP POST # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 201           # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()

1 scenario (1 passed)
5 steps (5 passed)
0m0.17s (9.65Mb)

And we know that if we can POST then our Background steps are also working.

This is good. It means we can get on with the process of testing all our other endpoints. And we will do just this in the very next video.

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