Healthcheck [Raw Symfony 4]


Our first Symfony 4 JSON API will be created using as few extra dependencies as possible. This will make our lives a little harder, but give us an understanding of what the specialised packages (FOSRESTBundle) and projects (Symfony API Platform) are doing to make our lives easier.

We're going to jump right in and start working with the Behat test project we created in the previous video.

However, this basic Symfony 4 JSON API project will be entirely standalone. This is to ensure our tests are re-usable amongst many different implementations.

In the "Beginners Symfony 4 Tutorial" we worked with a provided Symfony starting point called the Website Skeleton - symfony/website-skeleton.

The Website Skeleton is a really cool starting point that roughly equates to the same setup we got when installing a new Symfony 2 or Symfony 3 project. We get all the features like Twig, Doctrine, Routing, etc, all pre-configured for us.

That said, we don't need the vast majority of all of this extra stuff for our simple Symfony 4 JSON API.

It's up to you whether you start with the Website Skeleton, and remove the bits you don't need. Or you start with the symfony/skeleton and add the bits you do.

I'm going with the symfony/skeleton.

Creating Our Symfony 4 JSON API Project

We will get composer to do the hard work for us:

composer create-project symfony/skeleton symfony-4-json-api

Installing symfony/skeleton (v4.0.5)
  - Installing symfony/skeleton (v4.0.5) Loading from cache
Created project in checker
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 21 installs, 0 updates, 0 removals
  - Installing symfony/flex (v1.0.70) Loading from cache
  - Installing symfony/polyfill-mbstring (v1.7.0) Loading from cache
  - Installing symfony/console (v4.0.4) Loading from cache
  - Installing symfony/routing (v4.0.4) Loading from cache
  - Installing symfony/http-foundation (v4.0.4) Loading from cache
  - Installing symfony/yaml (v4.0.4) Loading from cache
  - Installing symfony/framework-bundle (v4.0.4) Loading from cache
  - Installing symfony/http-kernel (v4.0.4) Loading from cache
  - Installing symfony/event-dispatcher (v4.0.4) Loading from cache
  - Installing psr/log (1.0.2) Loading from cache
  - Installing symfony/debug (v4.0.4) Loading from cache
  - Installing symfony/finder (v4.0.4) Loading from cache
  - Installing symfony/filesystem (v4.0.4) Loading from cache
  - Installing psr/container (1.0.0) Loading from cache
  - Installing symfony/dependency-injection (v4.0.4) Loading from cache
  - Installing symfony/config (v4.0.4) Loading from cache
  - Installing psr/simple-cache (1.0.0) Loading from cache
  - Installing psr/cache (1.0.1) Loading from cache
  - Installing symfony/cache (v4.0.4) Loading from cache
  - Installing symfony/dotenv (v4.0.4) Loading from cache
Writing lock file
Generating autoload files
Symfony operations: 4 recipes (fd61733a414a8c78fdca41b78a50c4c2)
  - Configuring symfony/flex (>=1.0): From github.com/symfony/recipes:master
  - Configuring symfony/framework-bundle (>=3.3): From github.com/symfony/recipes:master
  - Configuring symfony/console (>=3.3): From github.com/symfony/recipes:master
  - Configuring symfony/routing (>=4.0): From github.com/symfony/recipes:master
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]

Some files may have been created or updated to configure your new packages.
Please review, edit and commit them: these files are yours.

 What's next? 

  * Run your application:
    1. Change to the project directory
    2. Execute the php -S 127.0.0.1:8000 -t public command;
    3. Browse to the http://localhost:8000/ URL.

       Quit the server with CTRL-C.
       Run composer require server --dev for a better web server.

  * Read the documentation at https://symfony.com/doc

Don't forget to change directory into your new project:

cd symfony-4-json-api

Ok, we will need some extra bits and pieces:

composer require symfony/form symfony/orm-pack symfony/monolog-bundle

You can skip the symfony/monolog-bundle if you'd like, but if things go wrong (as they often do), Monolog keeps around useful logs as to why.

In installing the ORM Pack we will have an extra entry added in to our .env file:

###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://dbuser:dbpassword@127.0.0.1:3306/basic_api
###< doctrine/doctrine-bundle ###

Update the DATABASE_URL to match whatever settings you've used for your database. These settings match those from the previous video where we set up our Dockerised MySQL instance.

There's a few extra dependencies that will help us during development:

composer require --dev symfony/maker-bundle symfony/profiler-pack symfony/web-server-bundle

The Maker bundle will give us a quick way to generate new Controllers, Forms, and Entities stubs.

The profiler pack gives us access to the /_profiler route, always super useful during developing a JSON API in my experience.

And the web server bundle gives us a nicer wrapper around the built in PHP web server.

That's the basics of our Symfony 4 project up and running.

Health Check

The first thing we will do is check that the Behat healthcheck.feature is quickly movable to a passing state.

Start by firing up your Symfony web server:

bin/console server:start

 [OK] Server listening on http://127.0.0.1:8000                                                                         

Now, update the Behat test project to use this new URL:

# behat.yaml

default:
    suites:
        default:
            contexts:
                - FeatureContext
                - Imbo\BehatApiExtension\Context\ApiContext
    extensions:
        Imbo\BehatApiExtension:
            apiClient:
-                base_uri: http://example.com
+                base_uri: http://127.0.0.1:8000

And send in a test run of the healthcheck.feature:

vendor/bin/behat features/healthcheck.feature

Feature: To ensure the API is responding in a simple manner

  In order to offer a working product
  As a conscientious software developer
  I need to ensure my JSON API is functioning

  Scenario: Basic healthcheck              # features/healthcheck.feature:8
    Given I request "/ping" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200          # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
      Expected response code 200, got 404. (Imbo\BehatApiExtension\Exception\AssertionFailedException)
    And the response body is:              # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyIs()
      """
      "pong"
      """

--- Failed scenarios:

    features/healthcheck.feature:8

1 scenario (1 failed)
3 steps (1 passed, 1 failed, 1 skipped)
0m0.23s (9.85Mb)

Ok, nice. Our JSON API /ping endpoint is being called, but it doesn't exist.

We will now create it:

# from your Symfony project

bin/console make:controller HealthcheckController

 created: src/Controller/HealthcheckController.php
 created: templates/healthcheck/index.html.twig

  Success! 

 Next: Open your new controller class and add some pages!

Note that we get a Twig template generated for us:

templates/healthcheck/index.html.twig

We don't need this. Feel free to remove it.

Twig is installed as part of the symfony/profiler-pack, and therefore the make command is trying to help us as best it understands our setup.

Our First Passing Test

Getting to a passing state is as simple as returning the expected string as a JSON response:

<?php

// src/Controller/HealthcheckController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HealthcheckController extends Controller
{
    /**
     * @Route("/ping", name="healthcheck")
     */
    public function index()
    {
        return new JsonResponse('pong');
    }
}

Now send in the test once again:

vendor/bin/behat features/healthcheck.feature

Feature: To ensure the API is responding in a simple manner

  In order to offer a working product
  As a conscientious software developer
  I need to ensure my JSON API is functioning

  Scenario: Basic healthcheck              # features/healthcheck.feature:8
    Given I request "/ping" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200          # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    And the response body is:              # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyIs()
      """
      "pong"
      """

1 scenario (1 passed)
3 steps (3 passed)
0m0.24s (9.54Mb)

Cool. It's a quick win. It validates our setup, on a basic or fundamental level, is working.

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