A presentation at PHPSW, November 2021 in in Bristol, UK by Rob Allen
Improve your API with OpenAPI Rob Allen PHPSW, November 2021
The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service https://spec.openapis.org/oas/latest.html Rob Allen ~ @akrabat
It’s about documentation Rob Allen ~ @akrabat
It’s about design-first Rob Allen ~ @akrabat
It’s about communicating changes Rob Allen ~ @akrabat
It’s about development workflows Rob Allen ~ @akrabat
It’s about a contract Rob Allen ~ @akrabat
Anatomy of the specification Rob Allen ~ @akrabat
openapi.yaml openapi: “3.1.0” # or “3.0.3” info: # … servers: # … paths: # … webhooks: # … components: # … security: # … tags: # … externalDocs: # … Rob Allen ~ @akrabat
openapi.yaml info: title: Rock-Paper-Scissors version: “1.0.0” description: > An implementation of Rock-Paper-Scissors. contact: name: “Rob Allen” servers: - url: https://rock-paper-scissors.example.com description: “RPS production API” Rob Allen ~ @akrabat
openapi.yaml paths: post: summary: Create a new game description: > Create a new game of Rock-Paper-Scissors. requestBody: # … responses: # … Rob Allen ~ @akrabat
openapi.yaml requestBody: description: Game to add required: true content: application/json: schema: $ref: ‘#/components/schemas/NewGameRequest’ Rob Allen ~ @akrabat
Reuse of objects $ref allows us to define one & use in many places components: schemas: GameId: type: string examples: - “2BC08389-885A-4322-80D0-EF0DE2D7CD37” format: “uuid” Player: type: string example: “Rob” Rob Allen ~ @akrabat
Build on other components schemas: NewGameRequest: properties: player1: $ref: ‘#/components/schemas/Player’ player2: $ref: ‘#/components/schemas/Player’ required: - player1 - player2 examples: - ‘{“player1”:”Ian”, “player2”:”Dave”}’ Rob Allen ~ @akrabat
openapi.yaml responses: “201”: $ref: ‘#/components/responses/NewGameResponse’ “400”: $ref: ‘#/components/responses/NewGameError’ “500”: $ref: ‘#/components/responses/InternalServerError’ Rob Allen ~ @akrabat
Writing your spec Rob Allen ~ @akrabat
Editing • Editor with plugins: vim, VS Code, etc • GUI: Stoplight, OpenAPI-GUI, Swagger Editor Rob Allen ~ @akrabat
Linting CLI tools: Spectral, openapi-spec-validator, etc. $ spectral lint rps-openapi.yaml …/slim4-rps-api/doc/rps-openapi.yaml 3:6 warning info-contact Info object must have “contact” object. info ✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints) Rob Allen ~ @akrabat
Docs Rob Allen ~ @akrabat
Docs Rob Allen ~ @akrabat
Docs Rob Allen ~ @akrabat
Developers Rob Allen ~ @akrabat
Mock server $ prism mock rps-openapi.yaml $ curl http://127.0.0.1:4010/games -d ‘{}’ {“message”:”Must provide both player1 and player2”} Rob Allen ~ @akrabat
Validation The schema section can be used to validate the request and response • Validate early and return a 422 • Validate that we return what we say we will • Put it in CI to prevent regressions Rob Allen ~ @akrabat
But I already have validation! Your code: • isn’t good enough! • isn’t reusable! • doesn’t match the docs! Rob Allen ~ @akrabat
But I already have validation! Your code: • isn’t good enough! • isn’t reusable! • doesn’t match the docs! However… Business logic validation still needed! Rob Allen ~ @akrabat
Validation in PHP OpenAPI 3.0: league/openapi-psr7-validator OpenAPI 3.1: opis/json-schema Rob Allen ~ @akrabat
Validation middleware public function process($request, $handler) { // Determine OAS schema for this route into $schema // Validate request $builder = (new ValidatorBuilder())->fromSchema($schema); $validator = $builder->getServerRequestValidator(); try { $match = $validator->validate($request); } catch (ValidationFailed $e) { throw new RequestValidationFailed($e); } Rob Allen ~ @akrabat
Validation middleware public function process($request, handler) { // Determine OAS schema for this route into $schema // Validate request $builder = (new ValidatorBuilder())->fromSchema($schema); $validator = $builder->getServerRequestValidator(); try { $match = $validator->validate($request); } catch (ValidationFailed $e) { throw new RequestValidationFailed($e); } Rob Allen ~ @akrabat
Validation middleware public function process($request, handler) { // Determine OAS schema for this route into $schema // Validate request $builder = (new ValidatorBuilder())->fromSchema($schema); $validator = $builder->getServerRequestValidator(); try { $match = $validator->validate($request); } catch (ValidationFailed $e) { throw new RequestValidationFailed($e); } Rob Allen ~ @akrabat
Validation middleware (cont) // Process request $response = $handler->handle($request); // Validate response try { $builder->getResponseValidator() ->validate($match, $response); } catch (ValidationFailed $e) { throw new ResponseValidationFailed($e); } return $response; } Rob Allen ~ @akrabat
Validation middleware (cont) // Process request $response = $handler->handle($request); // Validate response try { $builder->getResponseValidator() ->validate($match, $response); } catch (ValidationFailed $e) { throw new ResponseValidationFailed($e); } return $response; } Rob Allen ~ @akrabat
Validation middleware (cont) // Process request $response = $handler->handle($request); // Validate response try { $builder->getResponseValidator() ->validate($match, $response); } catch (ValidationFailed $e) { throw new ResponseValidationFailed($e); } return $response; } Rob Allen ~ @akrabat
Validation middleware (cont) // Process request $response = $handler->handle($request); // Validate response try { $builder->getResponseValidator() ->validate($match, $response); } catch (ValidationFailed $e) { throw new ResponseValidationFailed($e); } return $response; } Rob Allen ~ @akrabat
Validation Middleware in Laravel // composer install softonic/laravel-psr15-bridge // app/Http/Kernel.php use \League\OpenAPIValidation\PSR15\ValidationMiddleware; protected $routeMiddleware = [ // … ‘openapi-validation’ => ValidationMiddleware::class, ]; // Routes file Route::post(‘/game’, ‘GameController@create’) ->middleware(‘openapi-validation’); Rob Allen ~ @akrabat
To sum up Rob Allen ~ @akrabat
Resources • https://www.openapis.org • https://openapi.tools • https://github.com/akrabat/slim4-rps-api • https://github.com/thephpleague/openapi-psr7-validator • https://github.com/primitivesocial/openapi-initializer Rob Allen ~ @akrabat
Thank you! https://joind.in/talk/08577 Rob Allen ~ @akrabat
Photo credits - Scaffolding: https://www.flickr.com/photos/pagedooley/49683539647 - Writing: https://www.flickr.com/photos/throughkikslens/14516757158 - Books: https://www.flickr.com/photos/eternaletulf/41166888495 - Computer code: https://www.flickr.com/photos/n3wjack/3856456237 - Rocket launch: https://www.flickr.com/photos/gsfc/16495356966 - Stars: https://www.flickr.com/photos/gsfc/19125041621 Rob Allen ~ @akrabat
OpenAPI is the standard way to describe your HTTP API, adopted by most modern APIs. With the spec at the centre of your workflow, you can improve the quality of your API and development practices, and make integrations painless. With standardisation comes tooling and in this session, I will show you to leverage this to your advantage. We’ll cover what OpenAPI is, why it’s interesting and then how to use it. We will also look at how the OpenAPI specification enables you to create documentation, use mock servers and improve the robustness of our API with validation. This session is ideal for anyone who wants to write an API specification once and use it everywhere.
Here’s what was said about this presentation on social media.
@akrabat is teaching us all about the hidden gems in our HTTP RFCs. Did you know there's a Link header you can use to give developers links to your docs? 🤯 #scotphp21 pic.twitter.com/swvoNxf9nI
— Scotland PHP (@scotlandphp) October 23, 2021
You heard it here first. @akrabat doesn't want to see HTTP 200 responses with errors in the body! 😉 pic.twitter.com/TBrVr5qG0a
— Scotland PHP (@scotlandphp) October 23, 2021
We're getting Verbalicious with @akrabat talking about idempotent APIs pic.twitter.com/Q2RhlVNM8Y
— Scotland PHP (@scotlandphp) October 23, 2021