This is the last part of our post serie about building simple REST API with PHP. We can find the two previous ones here:
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.
Nice tutorial, but not easy to do for those who have just started using Slim. Here is another article that is targeted more at beginners: https://www.cloudways.com/blog/simple-rest-api-with-slim-micro-framework/
It is in step by step format and the basic concepts are explained as well.
Cool tutorial @olivedev, thanks for sharing.
Thanks for the tutorial, did you upload the code to github?
Hey Oscar, not unfortunately I didn’t have the chance to upload to GitHub.