Build simple REST API with PHP – Part 3

This is the last part of our post serie about building simple REST API with PHP. We can find the two previous ones here:

  1. Part 1
  2. Part 2

In the last part we’ll add Middlewares to add more features to our API and also customize some error outputs to make them more consumable.

 

Middlewares

We will add the following three middlewares:

  • ContentTypes: Parses JSON-formatted body from the client (the innermost);
  • JSON: utility middleware for “JSON only responses” and “JSON encoded bodies”;
  • Authentication (outermost).

 

ContentType Middleware

For the ContentType middleware we’ll use an existing Slim middleware, so we just need to add it in our bootstrap.php file:

1
2
3
4
...
// Middlewares
// Content Type (inner)
$app->add(new \Slim\Middleware\ContentTypes());

This middleware intercepts the HTTP request body and parses it into the appropriate PHP data structure if possible; else it returns the HTTP request body unchanged. This is particularly useful for preparing the HTTP request body for an XML or JSON API.

 

JSON Middleware

Our JSON middleware achieves two best practices: “JSON only responses” and “JSON encoded bodies”. So, lets add it in our bootstrap.php file:

1
$app->add(new API\Middleware\JSON());

As we can see if we review a classic Middleware class, the call() method of a Slim middleware is where the action takes place. Here is our JSON middleware class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
namespace API\Middleware;

class JSON extends \Slim\Middleware
{
    public function call()
    {
        try {
            // Force response headers to JSON
            $this->app->response->headers->set('Content-Type', 'application/json');

            $method = strtolower($this->app->request->getMethod());
            $mediaType = $this->app->request->getMediaType();
            $body = $this->app->request()->getBody();
            // Validate JSON format
            if (!$this->app->isJSON($body)) {
                throw new \Exception("JSON data is malformed", 400);
            }
            if (in_array($method, array('post', 'put', 'patch')) && '' !== $body) {
                if (empty($mediaType) || $mediaType !== 'application/json') {
                    $this->app->halt(415);
                }
            }
        } catch (\Exception $e) {
            echo json_encode(array(
                    'code' => $e->getCode(),
                    'message' => $e->getMessage()
                ),
                JSON_PRETTY_PRINT
            );
            exit;
        }
    }

    $this->next->call();
}

If the request method is one of the write-enabled ones (PUT, POST, PATCH) the request content type header must be application/json, if not the application exits with a 415 Unsupported Media Type HTTP status code. If all is right the statement $this->next->call() runs the next middleware in the chain.

 

Authentication Middleware

To authenticate our api users we’ll use a third-party library: JWT Authentication Middleware for Slim

1
2
3
$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

This middleware implements JSON Web Token Authentication for Slim Framework. It does not implement OAuth 2.0 authorization server nor does it provide ways to generate, issue or store authentication tokens. It only parses and authenticates a token when passed via header or cookie.

 

Pretty outputs for Errors

Our API should show useful error messages in pretty format. We need a minimal payload that contains an error code and message. With Slim we can redefine both 404 errors and server errors with the $app->notFound() and $app->error() method respectively.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$app->notFound(function () use ($app) {

    $mediaType = $app->request->getMediaType();
    $isAPI = (bool) preg_match('|^/api/v.*$|', $app->request->getPath());

    if ('application/json' === $mediaType || true === $isAPI) {

        $app->response->headers->set('Content-Type', 'application/json');

        echo json_encode(
            array(
                'code' => 404,
                'message' => 'Not found'
            )
        );

    }
});

For other errors we can use and customize the error() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$app->error(function (\Exception $e) use ($app, $log) {

    $mediaType = $app->request->getMediaType();
    $isAPI = (bool) preg_match('|^/api/v.*$|', $app->request->getPath());

    // Standard exception data
    $error = array(
        'code' => $e->getCode(),
        'message' => $e->getMessage()
    );

    // Custom error data (e.g. Validations)
    if (method_exists($e, 'getData')) {
        $errors = $e->getData();
    }
   
    if (!empty($errors)) {
        $error['errors'] = $errors;
    }

    $log->error($e->getMessage());
    if ('application/json' === $mediaType || true === $isAPI) {
        $app->response->headers->set('Content-Type', 'application/json');
        echo json_encode($error);
    }
});

The $app->error() method receives the thrown exception as argument and print it in a pretty way.

Well that’s, we’ve developed a basic API with common best practices. I hope these post series are helpful for you guys.

Build simple REST API with PHP – Part 2

In our first part of this post series we saw how to bootstrap our API and we defined the end points for it. Now, we’ll add the necessary logic to make our end points to work.

End Points

GET /users

Here we will get ALL our existing users.

1
2
3
4
5
6
7
8
9
10
11
12
13
// GET route for all users
$app->get('/users', function () use ($app, $log) {
    $users = array();
    $results = \ORM::forTable('user');

    $users = $results->findArray();

    // Here, we can add sorting, filtering and/or searching logic

    $output = array('code' => 200, 'data' => $users);
    echo json_encode($output, JSON_PRETTY_PRINT);
    return;
});

 

GET /users/:id

Given a user ID we return that user if exists, if not we return a 404 error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// GET route
$app->get('/users/:id', function ($id) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $user = $user->asArray();
           
        $output = array('code' => 200, 'data' => array($user));
        echo json_encode($output, JSON_PRETTY_PRINT);
        return;
    }
   
    $app->notFound();
});

Observe how the “:id” url parameter is passed as an argument of the callable function.

 

POST /users

Now, sending POST data to the /users end point we will be able to create a new User.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// POST route, for creating
$app->post('/users', function () use ($app, $log) {
    // Get POSTed data in body
    $body = $app->request()->getBody();

    // Validate data here

    $user = \ORM::for_table('user')->create();
    $user->set($body);
    if ($user->save() === true) {
        $output = array(
            'code' => 200,
            'data' => array($user->asArray())
        );
    echo json_encode($output, JSON_PRETTY_PRINT);
        return;
    } else {
        throw new \Exception('Something went wrong.');
    }
});

 

PUT /users/:id

Update an existing user with passed data given a user ID.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// PUT route, for updating
$app->put('/users/:id', function ($id) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $body = $app->request()->getBody();

        // Validate data here

        $user->set($body);
        if ($user->save() === true) {
            $user = $user->asArray();
           
            $output = array('code' => 200, 'data' => array($user));
            echo json_encode($output, JSON_PRETTY_PRINT);
            return;
        } else {
            throw new \Exception('Something went wrong.');
        }
    }
   
    $app->notFound();
});

 

DELETE /users/:id

Here given a user ID we will delete the user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// DELETE route
$app->delete('/users/:id', function ($id) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $user->delete();
        if ($user->save() === true) {
            $output = array('code' => 200, 'data' => array());
            echo json_encode($output, JSON_PRETTY_PRINT);
            return;
        } else {
            throw new \Exception('Something went wrong.');
        }
    }
   
    $app->notFound();
});

 

User’s Phonenumbers

Now, we will define the end points for our second collection Phonenumbers.

GET /users/:id/phonenumbers

In this end point we will return all the associated phonenumbers to a user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// GET route for all users's phonenumbers
$app->get('/users/:id/phonenumbers', function ($id) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $data = $user->asArray();
        $phonenumbers = \ORM::forTable('phonenumber')
            ->where('user_id', $id)
            ->orderByDesc('id')
            ->findArray();
        $data['phonenumbers'] = $phonenumbers;
        $output = array('code' => 200, 'data' => $data);
        echo json_encode($output, JSON_PRETTY_PRINT);
        return;
    }
   
    $app->notFound();
});

 

GET /users/:id/phonenumbers/:pid

Now given a user ID and a phone ID we will return that specific phonenumber for the given user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// GET route for specific user's phonenumber
$app->get('/users/:id/phonenumbers/:pid', function ($id, $phoneId) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $data = $user->asArray();
        $phonenumber = \ORM::forTable('phonenumber')
            ->where('user_id', $id)
            ->andWhere('id', $phoneId)
            ->findOne();
        if ($phonenumber) {
            $data['phonenumber'] = $phonenumber;
            $output = array('code' => 200, 'data' => $data);
            echo json_encode($output, JSON_PRETTY_PRINT);
            return;
        }
    }
   
    $app->notFound();
});

 

POST /users/:id/phonenumbers

With this end point we will be able to create/add a phonenumber to a user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// POST route, for creating a new user's phonenumber
$app->post('/users/:id/phonenumbers', function ($id) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $data = $user->asArray();
        // Get POSTed data in body
        $body = $app->request()->getBody();

        // Validate data here

        $phonenumber = \ORM::for_table('phonenumber')->create();
        $phonenumber->set($body);
        if ($phonenumber->save() === true) {
            $data['phonenumber'] = $phonenumber->asArray();
            $output = array(
                'code' => 200,
                'data' => $data
            );
        echo json_encode($output, JSON_PRETTY_PRINT);
            return;
        } else {
            throw new \Exception('Something went wrong.');
        }
    }
   
    $app->notFound();
});

 

PUT /users/:id/phonenumbers/:pid

PUT route to update a user’s phonenumber.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// PUT route, for updating a user's phonenumber
$app->put('/users/:id/phonenumbers/:pid', function ($id, $phoneId) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $data = $user->asArray();
        $phonenumber = \ORM::forTable('phonenumber')
            ->findOne($phoneId);

        if ($phonenumber) {
            $data = $user->asArray();
            // Get POSTed data in body
            $body = $app->request()->getBody();

            // Validate data here

            $phonenumber->set($body);
            if ($phonenumber->save() === true) {
                $data['phonenumber'] = $phonenumber->asArray();
                $output = array(
                    'code' => 200,
                    'data' => $data
                );
            echo json_encode($output, JSON_PRETTY_PRINT);
                return;
            } else {
                throw new \Exception('Something went wrong.');
            }
        }
    }
   
    $app->notFound();
});

 

DELETE /users/:id/phonenumbers/:pid

And at the end, we define the route to delete an specific user’s phonenumber.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DELETE route, for delete a user's phonenumber
$app->delete('/users/:id/phonenumbers/:pid', function ($id, $phoneId) use ($app, $log) {
    $user = \ORM::forTable('user')
        ->findOne($id);

    if ($user) {
        $phonenumber = \ORM::forTable('phonenumber')
            ->findOne($phoneId);
        if ($phonenumber) {
            $phonenumber->delete();
            if ($phonenumber->save() === true) {
                $output = array('code' => 200, 'data' => array());
                echo json_encode($output, JSON_PRETTY_PRINT);
                return;
            } else {
                throw new \Exception('Something went wrong.');
            }
        }
    }
   
    $app->notFound();
});

 

Well, we finished defining all our end points to interect against our API. At this point we have a basic API working but still there are thing to do to make it more fully functional. For instance, what if we want to make our API secure? We should add authentication, and we’ll do it using really great Slim’s functionality Middlewares. Also, we can ensure all our requests/responses are being talking JSON and/or add some rate limit to don’t allow overwhelming our API.

In the next and last post we will cover all this. See you soon! :D.

Build simple REST API with PHP – Part 1

A web API (Application Programming Interface) is a set of programming instructions to access software applications and consume and share data between them. Developing your own API you can benefit both you and your users and for that we will write about how to build a simple REST API with PHP.

To build our API we will use Slim micro framework. As they mention in their site

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.

As we said above an API is an Application Programming Interface for what it should be friendly, simple and easy to use. To help other developers to use our API we should consider writing a good documentation about it, or at least, a very explanatory README file. Such documentation need to be a summary of the service’s scope and the list of methods and access points.

Following the key principles of REST, each resource is represented by a URL, where the action is the HTTP verb used to access it.

A full list of access points is listed below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
URL                               HTTP Verb    Operation
/api/users                        GET          Returns an array of users
/api/users/:id                    GET          Returns the user with id of :id
/api/users                        POST         Adds a new user and return it with its id
/api/users/:id                    PATCH        Partially updates the user with id of :id
/api/users/:id                    PUT          Updates the complete user with id of :id
/api/users/:id                    DELETE       Deletes the user with id of :id

/api/users/:id/phonenumbers       GET          Returns the phonenumbers for the user with id of :id
/api/users/:id/phonenumbers/:pid  GET          Returns the phonenumber with id of :pid for the user with id of :id
/api/users/:id/phonenumbers       POST         Adds a new phonenumber for the user with id of :id
/api/users/:id/phonenumbers/:pid  PATCH        Partially updates the phonenumber with id of :pid for the user with id of :id
/api/users/:id/phonenumbers/:pid  PUT          Updates the phonenumber with id of :pid for the user with id of :id
/api/users/:id/phonenumbers/:pid  DELETE       Deletes the phonenumber with id of :pid for the user with id of :id

The above README is a good start point, but a more complete documentation would be better. You can consider some libraries to generate documentation like Google APIs Discovery Service, apiDoc or Swagger.

Setup

We define our composer.json with our project information and dependencies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
    "name"
: "yourname/users-management",
    "description"
: "Simple RESTful API for users management",
    "license"
: "MIT",
    "authors"
: [
        {
            "name"
: "Your Name",
            "email"
: "you@yourdomain.com"
        }
    ],
    "require"
: {
        "slim/slim"
: "*",
        "slim/extras"
: "*",
        "slim/middleware"
: "*",
        "monolog/monolog"
: "*",
        "j4mie/paris"
: "*",
        "flynsarmy/slim-monolog"
: "*"
    },
    "archive"
: {
        "exclude"
: ["vendor", ".DS_Store", "*.log"]
    },
    "autoload"
: {
        "psr-0"
: {
            "API"
: "lib/"
        }
    }
}

As we can see in our composer.json, along with Slim we’re using Paris (lightweight Active Record implementation on top of Idiorm) to access the database layer and Monolog for logging.

Bootstrap our API

The bootstrap.php file is in charge to autoload classes and load configurations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Init application mode
if (empty($_ENV['SLIM_MODE'])) {
    $_ENV['SLIM_MODE'] = (getenv('SLIM_MODE'))? getenv('SLIM_MODE') : 'dev';
}

// Init and load configuration
$config = array();
$configFile = dirname(__FILE__) . '/config/config.'. $_ENV['SLIM_MODE'] . '.php';

if (is_readable($configFile)) {
    require_once $configFile;
} else {
    require_once dirname(__FILE__) . '/config/config.default.php';
}

// Create App
$app = new API\Application($config['app']);

// Get log writer
$log = $app->getLog();

// Init database
try {
    if (!empty($config['db'])) {
        \ORM::configure($config['db']['dsn']);
        if (!empty($config['db']['username'])
            && !empty($config['db']['password'])) {
            \ORM::configure('username', $config['db']['username']);
            \ORM::configure('password', $config['db']['password']);
        }
    }
} catch (\PDOException $e) {
    $log->error($e->getMessage());
}

// Add some Middlewares here (we discuss about this later)

First, we get the environment we are using (could be dev, staging, prod or whatever you want and define) and then we load the environment related configuration, it there is not, we load default configuration. Then we create the application which as you can see it’s an Application class which extends the basic Slim class; and at the end we try to connect to the database.

Later we will add some Middleware to our app, you can read about what a Middleware is here.

Front Controller

Our front controller index.php is the unique entry point to our API and it’s where the magic happens. Here we’ll define the HTTP verbs to interact with our API and be able to list/create/edit/delete our “Users” and “Phonenumbers“.

Here is our front controller where we’ll start writing our methods (HTTP verbs):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
require_once dirname(__FILE__) . '/bootstrap.php';

// API group
$app->group('/api', function () use ($app, $log) {

    // Version group
    $app->group('/v1', function () use ($app, $log) {

        // GET route for all users
        $app->get('/users', function () use ($app, $log) {
            // Some code here
        });

        // GET route
        $app->get('/users/:id', function ($id) use ($app, $log) {
            // Some code here
        });

        // POST route, for creating
        $app->post('/users', function () use ($app, $log) {
            // Some code here
        });

        // PUT route, for updating
        $app->put('/users/:id', function ($id) use ($app, $log) {
            // Some code here
        });

        // DELETE route
        $app->delete('/users/:id', function ($id) use ($app, $log) {
            // Some code here
        });


        // GET route for all users's phonenumbers
        $app->get('/users/:id/phonenumbers', function ($id) use ($app, $log) {
            // Some code here
        });

        // GET route for specific user's phonenumber
        $app->get('/users/:id/phonenumbers/:pid', function ($id, $phoneId) use ($app, $log) {
            // Some code here
        });

        // POST route, for creating a new user's phonenumber
        $app->post('/users/:id/phonenumbers', function ($id) use ($app, $log) {
            // Some code here
        });

        // PUT route, for updating a user's phonenumber
        $app->put('/users/:id/phonenumbers/:pid', function ($id, $phoneId) use ($app, $log) {
            // Some code here
        });

        // DELETE route, for delete a user's phonenumber
        $app->delete('/users/:id/phonenumbers/:pid', function ($id, $phoneId) use ($app, $log) {
            // Some code here
        });
    });
});

We’ve created two nested groups, /api and /v1, so we can easily adhere to the “versioning in the URL” best practice to versioning APIs.

So far we’ve installed needed libraries, bootstrapped the app and defined the routes for all the end points our API will have. In the next parts of this post series we will add the logic for every method we’ve defined and we’ll add and write some Middlewares to make a fully functional API.

Keep in touch! 😀

HTTP Errors

A list of most known HTTP errors.

These errors belong to the HTTP/1.1 standard and they are classified in five different types.
The five different defined types are:

1xx. Informational. The request is received and it continues with the process. The errors in this range indicate provisional responses. The Web Servers should not send 1xx errors to the client, except under experimental conditions.

2xx. Success. These errors indicate that the request was received, understand, accepted and successfully processed.

3xx. Redirection. In this range the client should do aditional actions to complete the request. The required action should be carry by the User Agent with no user interaction only if the method is GET or HEAD. The user agent should not automatically redirect more than five times, if not is considered an infinite loop.

4xx. Client Errors. These errors are triggered when the error is in the client.

5xx. Server Errors. The server fails when it looks like the request is not valid or the server is unable to do the request.

1xx Informational.

100 Continue. The server has received the request headers and the client should proceed to send the request body.

101 Switching Protocols. The client asks to the server to change protocols and the server knows if it could do it.

102 Processing. As a request can contain many sub-headers, this could take so much time to complete the request. This error indicates that the server has received and is processing the request, but it does not response that it’s not available yet. This avoids the client to assume that the request was lost.

2xx Success.

200 OK. This is the standard response for a successful HTTP request. The response will depend in the used method (GET, PUT, POST, etc).

201 Created. The request has been completed and the response is that a new resource was created.

202 Accepted. The request has been accepted to be processed, but it has not been completed yet.

203 Non-Authoritative Information. The server has processed the request successfully, but it’s returning information what could be from another source.

204 No Content. The server processed the request successfully, but there is not content to return.

205 Reset Content. It’s identical to 204 error but with the difference of the response requires the client to reset the resource.

206 Partial Content. The server is returning the resource partially. This error is used in tools like wget.

207 Multi-Status. The body of the response is a XML message and it could contain separated status depending in how much sub-requests were done.

3xx Redirect.

300 Multiple Choices. It indicates multiple options for the resource the user is requesting. For instance, it could be used to present different options to video formats.

301 Moved Permanently. The current request and all future requests to the requested resource should be redirected to the given url.

302 Found. This is the most popular redirect error, the HTTP/1.0 requires the client to make a temporal redirect, but the most popular browsers implement 302 error as 303 error. Therefore, HTTP/1.1 added the 303 and 307 errors to distinguish between these two behaviors, most web applications still use the 302 errors like a 303 error.

303 See Other. This error was added in HTTP/1.1. The response for the rrequest could be find in other URI using the GET method.

304 Not Modified. It indicates that the resource was not modified since the last request.

305 Use Proxy. This error was added in HTTP/1.1. Some HTTP clients (like Mozilla and Internet Explorer) do not handle this error properly, mainly due to security reasons.

307 Temporary Redirect. This error was also added in HTTP/1.1. In this case, the request should be repeat it with another URI, but the future requests could use the original URI. In contrast with 303 error, the HTTP method should not be changed.

4xx Client Errors.

400 Bad Request. The request contains wrong syntaxis or it could not be completed.

401 Unauthorized. This error is similar to 403, but it is specifically used when the user can authenticate but the authentication has been failed or has not been entered the authentication credentials yet.

402 Payment Required. This error is reserved for future uses and it’s thinking to be used for applications those make payments.

403 Forbidden. The request was legal, but the server rejects to response it.

404 Not Found. The requested resource is not found, but it could be in the future.

405 Method Not Allowed. The resource was requested via a not allowed HTTP method. For instance, try to process a form with a GET method instead of a POST.

406 Not Acceptable. The requested resource only can generate content no acceptable by the request headers.

407 Proxy Authentication Required.

408 Request Timeout. The server triggered a timeout.

409 Conflict. It indicates tha the request could not be processed due to a conflict.

410 Gone. It indicates that the requested resource is not available anymore. This error should be used when a resource was intentionally removed, although a 404 error could be returned.

411 Length Required. The request does not specify the content length which is a requirement for the requested resource.

412 Precondition Failed. The server does not accomplish with some pre-conditions that the user is requesting.

413 Request Entity Too Large. The request is to big to be processed.

414 Request-URI Too Long. The entered URI was too long to be processed.

415 Unsupported Media Type. The request does not specify none type of media. For instance, the client specifies the requested resource as image/svg+xml format, but the server does not find a resource with that format.

417 Expectation Failed. The server could not meet the requirements in the request headers.

422 Unprocessable Entity. The request was valid but the server was unable to attend it due to some semantic errors.

423 Locked. The resource which is being accessing is locked.

424 Failed Dependency. The request failed due to previous failed requests.

426 Upgrade Required. The client should be changed to a different protocol, for instance to TLS/1.0.

5xx Server Errors.

500 Internal Server Error. It is a generic error message and it’s returned when there is not a more specific error.

501 Not Implemented. The server neither recognizes the HTTP request method nor have the ability to complete the request.

502 Bad Gateway. The server was working as a gateway or proxy and it received an invalid response from the downstream server.

503 Service Unavailable. The server is not currently available (due to an overload or because it’s in maintenance). Generally, this error is temporal.

504 Gateway Timeout. The server did not receive a response in time.

505 HTTP Version Not Supported. The server does not support the HTTP protocol version used by the request.