Update: API Platform now has a dedicated website with an up to date version of the following tutorial.

PHP celebrates its 20 years this week. In 20 years, the web changed dramatically and is now evolving faster than ever:

PHP.net, Symfony, Facebook and many others have worked hard to improve and professionalize the PHP ecosystem. The PHP world has closed the gap with most backend solutions and is often more innovative than them.

But in critical area I’ve described previously, many things can be improved. Almost all existing solutions are still designed and documented to create websites the old way: a server generate then send plain-old HTML documents to browsers.

What a better gift for the PHP birthday than a brand new set of tools to kickstart modern web projects? Here comes Dunglas’s API platform, a framework for API-first projects built on top of Symfony! Like other modern frameworks such as Zend Framework and Symfony, it’s both a full-stack all-in-one framework and a set of independent PHP components and bundles that can be used separately.

The architecture promoted by the framework will distrust many of you but read until the end and you will see how API Platform make modern development easy and fun again:

Start by creating a hypermedia REST API exposing structured data that can be understood by any compliant client such your apps but also as search engines (JSON-LD with Schema.org vocabulary). This API is the central and unique entry point to access and modify data. It also encapsulates the whole business logic.

exposing structured data that can be understood by any compliant client such your apps but also as search engines (JSON-LD with Schema.org vocabulary). This API is the central and unique entry point to access and modify data. It also encapsulates the whole business logic. Then create as many clients as you want using frontend technologies you love: an HTML5/Javascript webapp querying the API in AJAX (of course) but also a native iOS or Android app, or even a desktop application. Clients only display data and forms.

Let’s see how to do that. In this tutorial, we will create a typical blog application with API Platform.

If you are in a hurry, a demo is available online and all sources created during this tutorial are available on GitHub:

the blog API : demo (we recommend to browse it with Postman) / sources

the Angular client: demo / sources

To create the API-side of our project we will:

Bootstrap a fully featured and working data model including ORM mapping, validation rules and semantic metadata with the generator provided by API platform (of course you can also handcraft your data model or modify the generated one to fit your needs).

Expose this data model trough a read/write (CRUD) API following JSON-LD and Hydra Core Vocabulary open standards, having Schema.org metadata and with a ton of features out of the box: pagination, validation, error serialization, filters and a lot of other awesome features (here too, everything is extensible thanks to a powerful event system and strong OOP).

Then we will develop a tiny AngularJS webapp to illustrate how to create and consume data from the API. Keep in mind that you can use your preferred client-side technology (tested and approved with Angular, React, Ionic, Swift but can work with any language able to send HTTP requests).

Prerequisites

Only PHP 5.5+ must be installed to run Dunglas’s API Platform. A built-in web server is shipped with the framework for the development environment.

To follow this tutorial a database must be installed (but its not a strong dependency of the framework). I recommend MySQL or MariaDB but other major DBMS are supported including SQLite, PostgreSQL, Oracle and SQL server are supported trough Doctrine.

Installing the framework

Let’s start our new blog API project. The easiest way to create a new project is to use Composer (you need to have it installed on your box):

composer create-project dunglas/api-platform --stability=beta blog-api

Composer creates the skeleton of the new blog API then retrieve the framework and all its dependencies.

At the end of the installation, you will be prompted for some configuration parameters including database credentials. All configuration parameters can be changed later by editing the app/config/parameters.yml file.

Dunglas’s API Platform is preconfigured to use the popular Doctrine ORM. It is supported natively by all API Platform components. However the Doctrine ORM is fully optional: you can replace it by your favorite ORM, no ORM at all and even no database.

The installer will ask for:

mail server credentials (to send mails)

the locale of the application

the URL of your default web client application to automatically set appropriate CORS headers, set it to http://locahost:9000 (the default URL of the built-in Grunt server) to follow this tutorial

a name and a description of the API that will be used in the generated documentation

a secret token (choose a long one) for cryptographic features

Take a look at the content of the generated directory. You should recognize a Symfony application directory structure. It’s fine and intended: the generated skeleton is a perfectly valid Symfony full-stack application that follows Symfony Best Practices. It means that you can:

use thousands of exiting Symfony bundles with API Platform

use API Platform in any existing Symfony application

reuse all your Symfony skills and benefit of the high quality Symfony documentation

The skeleton comes with a demonstration bookstore API. Remove it:

empty app/config/schema.yml and app/config/services.yml

and delete all files in the src/AppBundle/Entity/ directory

Generating the data model

The first incredibly useful tool provided by Dunglas’s API platform is it’s data model generator also know as PHP Schema. This API Platform component can also be used standalone to bootstrap any PHP data model.

To kickstart our blog data model we browse Schema.org and find an existing schema that describe perfectly what we want: https://schema.org/BlogPosting

The schema command line tool will instantly generate a PHP data model from the Schema.org vocabulary:

Browse Schema.org, choose the types and properties you need (there is a bunch of schemas available), run our code generator. You’re done! You get a fully featured PHP data model including:

A set of PHP entities with properties, constants (enum values), getters, setters, adders and removers. The class hierarchy provided by Schema.org will be translated to a PHP class hierarchy with parents as abstract classes. The generated code complies with PSR coding standards.

classes. The generated code complies with PSR coding standards. Full high-quality PHPDoc for classes, properties, constants and methods extracted from Schema.org.

Doctrine ORM annotation mapping including database columns with type guessing, relations with cardinality guessing, class inheritance (through the @AbstractSuperclass annotation).

annotation). Data validation through Symfony Validator annotations including data type validation, enum support (choices) and check for required properties.

Interfaces and Doctrine ResolveTargetEntityListener support.

support. List of values provided by Schema.org with PHP Enum classes.

Reusing an existing semantic schema has many advantages:

Don’t Reinvent The Wheel

Data models provided by Schema.org are popular and have been proved efficient. They cover a broad spectrum of topics including creative work, e-commerce, event, medicine, social networking, people, postal address, organization, place or review. Schema.org has its root in a ton of preexisting well designed vocabularies and is successfully used by more and more website and applications.

Pick up schemas applicable to your application, generate your PHP model, then customize and specialize it to fit your needs.

Improve SEO and user experience

Adding Schema.org markup to websites and apps increase their ranking in search engines results and enable awesome features such as Google Rich Snippets and Gmail markup.

Mapping your app data model to Schema.org structures can be a tedious task. Using the generator, your data model will be a derived from Schema.org. Serializing your data as JSON-LD will not require specific mapping nor adaptation. It’s a matter of minutes.

Be ready for the future

Schema.org improves the interoperability of your applications. Used with hypermedia technologies such as Hydra it’s a big step towards the semantic and machine readable web. It opens the way to generic web API clients able to extract and process data from any website or app using such technologies.

To generate our data model form Schema.org types, we must create a YAML configuration file for PHP schema:

# app/config/schema.yml annotationGenerators: # Generators we want to use, keep it as is for any API Platform project - SchemaOrgModel\AnnotationGenerator\PhpDocAnnotationGenerator - SchemaOrgModel\AnnotationGenerator\DoctrineOrmAnnotationGenerator - SchemaOrgModel\AnnotationGenerator\ConstraintAnnotationGenerator - SchemaOrgModel\AnnotationGenerator\DunglasApiAnnotationGenerator namespaces: entity: AppBundle\Entity # The default namespace for entities, following API Platform and Symfony best practices types: # The list of type to generated (a PHP entity class by type will be generated) BlogPosting: ~ # A type to generate a PHP entity class from, including all its properties (here this type has no specific property, they are all inherited) Article: # Schema.org has an inheritance system, we will configure all types of the hierarchy properties: # The list of properties we want to use articleBody: ~ articleSection: ~ CreativeWork: properties: author: range: Person # PHP Schema handle relations. Here we force the type of the property to Person cardinality: (*..0) # Force the cardinality of the relation headline: ~ isFamilyFriendly: ~ datePublished: ~ Thing: properties: name: ~ Person: # Person is a relation of the "CreativeWork" type (property "author"), PHP Schema will generate relations for us properties: {} # We don't want any specific property for a person except "name" inherited from Thing

Then execute the generator:

bin/schema generate-types src/ app/config/schema.yml

Take a look at the content of the src/AppBundle/Entity/ directory. PHP Schema generated for us a set of Plain-Old-PHP entities representing our data model. As promised our entities include:

type documentation from Schema.org and converted it to PHPDoc

Doctrine ORM mapping annotations (including for relations)

Symfony validation annotations

Schema.org IRI mapping (the @Iri annotations), we will see later that the API bundle use them to expose structured semantic data

and they follow the PSR-2 coding style

The data model is fully functional. You can hack it (modify entities, properties, indexes, validation rules…), or use it as is!

Ask Doctrine to create the database of the project:

app/console doctrine:database:create

Then generate database tables related to the generated entities:

app/console doctrine:schema:create

PHP Schema provides a lot of configuration options. Take a look at its dedicated documentation. Keep in mind that PHP Schema is also available as a standalone tool (and a PHAR will be available soon) and can be used to bootstrap any PHP project (works fine with raw PHP, API Platform and Symfony but has an extension mechanism allowing to use it with other technologies such as Zend Framework and Laravel).

Sometimes we will have to make a data model with very specific business types, not available in Schema.org. Sometimes we will find Schema.org types that partially matches what we want but needs to be adapted.

Keep in mind that you can always create your very own data model from scratch. It’s perfectly OK and you can still use API Platform without PHP Schema.

Anyway, PHP Schema is a tool intended to bootstrap the data model. You can and you will edit manually generated PHP entities. When you start to edit manually the generated files, be careful to not run the generator again, it will overwrite your changes (this behavior will be enhanced in future versions). When you do such things, the best to do is to remove dunglas/php-schema from your composer.json file.

Exposing the API

We have a working data model backed by a database. Now we will create a hypermedia REST API thanks to another component of Dunglas’s API Platform: DunglasApiBundle.

As PHP Schema, it is already preinstalled and properly configured. We just need to declare resources we want to expose.

Exposing a resource collection basically consist to register a new Symfony service. For our blog app we will expose trough the API the two entity classes generated by PHP Schema: BlogPosting (blog post) and Person (author of the post):

# app/config/services.yml services: resource.blog_posting: parent: "api.resource" arguments: [ "AppBundle\\Entity\\BlogPosting" ] tags: [ { name: "api.resource" } ] resource.person: parent: "api.resource" arguments: [ "AppBundle\\Entity\\Person" ] tags: [ { name: "api.resource" } ]

And our API is already finished! How would it be easier?

Start the integrated development web server: app/console server:start

Then open http://localhost:8000/doc with a web browser:

Thanks to NelmioApiDocBundle support of DunglasApiBundle and its integration with API Platform, you get for a free an automatically generated human-readable documentation of the API (Swagger-like). The doc also includes a sandbox to try the API.

You can also use your favorite HTTP client to query the API. I strongly recommend Postman. It is lower level than the sandbox and will allow to inspect forge and inspect JSON requests and responses easily.

Open http://localhost:8000 with Postman. This URL is the entry point of the API. It gives to access to all exposed resources. As you can see, the API returns minified JSON-LD. For better readability, JSON snippets have been prettified in this document.

Trying the API

Add a person named Kévin by issuing a POST request on http://localhost:8000/users with the following JSON document as raw body:

{"name": "Kévin"}

The data is inserted in database. The server replies with a JSON-LD representation of the freshly created resource. Thanks to PHP Schema, the @type property of the JSON-LD document is referencing a Schema.org type:

{ "@context": "/contexts/Person", "@id": "/people/1", "@type": "http://schema.org/Person", "name": "Kévin" }

The JSON-LD spec is fully supported by the bundle. Want a proof? Browse http://localhost:8000/contexts/Person .

By default, the API allows GET (retrieve, on collections and items), POST (create), PUT (update) and DELETE (self-explaining) HTTP methods. You can add and remove any other operation you want. Try it!

Now, browse http://localhost:8000/people :

{ "@context": "/contexts/Person", "@id": "/people", "@type": "hydra:PagedCollection", "hydra:totalItems": 1, "hydra:itemsPerPage": 30, "hydra:firstPage": "/people", "hydra:lastPage": "/people", "hydra:member": [ { "@id": "/people/1", "@type": "http://schema.org/Person", "name": "Kévin" } ] }

Pagination is also supported (and enabled) out of the box.

It’s time to post our first article. Run a POST request on http://locahost:8000/blog_posting with the following JSON document as body:

{ "name": "Dunglas's API Platform is great", "headline": "You'll love that framework!", "articleBody": "The body of my article.", "articleSection": "technology", "author": "/people/1", "isFamilyFriendly": "maybe", "datePublished": "2015-05-11" }

Oops… the isFamilyFriendly property is a boolean. Our JSON contains an incorrect string. Fortunately the bundle is smart enough to detect the error: it uses Symfony validation annotations generated by PHP Schema previously. It returns a detailed error message in the Hydra error serialization format:

{ "@context": "/contexts/ConstraintViolationList", "@type": "ConstraintViolationList", "hydra:title": "An error occurred", "hydra:description": "isFamilyFriendly: This value should be of type boolean.

", "violations": [ { "propertyPath": "isFamilyFriendly", "message": "This value should be of type boolean." } ] }

Correct the body and send the request again:

{ "name": "Dunglas's API Platform is great", "headline": "You'll love that framework!", "articleBody": "The body of my article.", "articleSection": "technology", "author": "/people/1", "isFamilyFriendly": true, "datePublished": "2015-05-11" }

We fixed it! By the way you learned how to to work with relations. In a hypermedia API, every resource is identified with an unique IRI (an URL is a an IRI). They are in the @id property of every JSON-LD document generated by the API and you can use it as reference to set relations like we done in the previous snippet for the author property.

Dunglas’s API Platform is smart enough to understand any date format supported by PHP date functions. In production we recommend the format specified by the RFC 3339.

We already have a powerful hypermedia REST API (always without writing a single line of PHP), but there is more.

Our API is auto-discoverable. Open http://localhost:8000/vocab and take a look at the content. Capabilities of the API are fully described in a machine-readable format: available resources, properties and operations, description of elements, readable and writable properties, types returned and expected…

As for errors, the whole API is described using the Hydra Core Vocabulary, an open web standard for describing hypermedia REST APIs in JSON-LD. Any Hydra-compliant client or library is able to interact with the API without knowing anything about it! The most popular Hydra client is Hydra Console. Open an URL of the API with it you’ll get a nice management interface.

You can also give a try to the brand new hydra-core Javascript library.

DunglasApiBundle offers a lot of other features including:

Read its dedicated documentation to see how to leverage them and how to hook your own code everywhere into it.

Specifying and testing the API

Behat (a Behavior-driven development framework) is preconfigured with contexts useful to spec and test REST API and JSON documents.

With Behat, you can write the API specification (as user stories) in natural language then execute scenarios against the application to validate its behavior.

Create a Gherkin feature file containing the scenarios we run manually in the previous chapter:

Feature: Blog In order to post news As a client software developer I need to be able to retrieve, create, update and delete authors and posts trough the API. # "@createSchema" creates a temporary SQLite database for testing the API @createSchema Scenario: Create a person When I send a "POST" request to "/people" with body: """ {"name": "Kévin"} """ Then the response status code should be 201 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { "@context": "/contexts/Person", "@id": "/people/1", "@type": "http://schema.org/Person", "name": "Kévin" } """ Scenario: Retrieve the user list When I send a "GET" request to "/people" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { "@context": "/contexts/Person", "@id": "/people", "@type": "hydra:PagedCollection", "hydra:totalItems": 1, "hydra:itemsPerPage": 30, "hydra:firstPage": "/people", "hydra:lastPage": "/people", "hydra:member": [ { "@id": "/people/1", "@type": "http://schema.org/Person", "name": "Kévin" } ] } """ Scenario: Throw errors when a post is invalid When I send a "POST" request to "/blog_postings" with body: """ { "name": "Dunglas's API Platform is great", "headline": "You'll that framework!", "articleBody": "The body of my article.", "articleSection": "technology", "author": "/people/1", "isFamilyFriendly": "maybe", "datePublished": "2015-05-11" } """ Then the response status code should be 400 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And the JSON should be equal to: """ { "@context": "/contexts/ConstraintViolationList", "@type": "ConstraintViolationList", "hydra:title": "An error occurred", "hydra:description": "isFamilyFriendly: This value should be of type boolean.

", "violations": [ { "propertyPath": "isFamilyFriendly", "message": "This value should be of type boolean." } ] } """ # "@dropSchema" is mandatory to cleanup the temporary database on the last scenario @dropSchema Scenario: Post a new blog post When I send a "POST" request to "/blog_postings" with body: """ { "name": "Dunglas's API Platform is great", "headline": "You'll that framework!", "articleBody": "The body of my article.", "articleSection": "technology", "author": "/people/1", "isFamilyFriendly": true, "datePublished": "2015-05-11" } """ Then the response status code should be 201 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json" And print last JSON response And the JSON should be equal to: """ { "@context": "/contexts/BlogPosting", "@id": "/blog_postings/1", "@type": "http://schema.org/BlogPosting", "articleBody": "The body of my article.", "articleSection": "technology", "author": "/people/1", "datePublished": "2015-05-11T00:00:00+02:00", "headline": "You'll that framework!", "isFamilyFriendly": true, "name": "Dunglas's API Platform is great" } """

The API Platform flavor of Behat also comes with a temporary SQLite database dedicated to tests. It works out of the box.

Just run bin/behat . Everything should be green:

4 scenarios (4 passed) 21 steps (21 passed)

Then you get a powerful hypermedia API exposing structured data, specified and tested thanks to Behat. And still without a line of PHP!

It’s incredibly useful for prototyping and Rapid Application Development (RAD). But the framework is designed to run in prod. It benefits from strong extension points and is has been optimized for very high-traffic websites (API Platform powers the new version of a major world-wide media site).

Other features

Dunglas’s API Platform has a lot of other features and can extended with PHP libraries and Symfony bundles. Stay tuned, more documentation and cookbooks are coming!

Here is a non exhaustive list of what you can do with API Platform:

The Angular client

Prequistes: Node.js and NPM must be installed.

Dunglas’s API Platform is agnostic of the client-side technology. You can use whatever platform, language or framework you want. As an illustration of doors opened by an API-first architecture and the ease of development of SPA, we will create a tiny AngularJS client.

Keep in mind that this is not an AngularJS tutorial and it doesn’t pretend to follow AngularJS best practices. It’s just an example of how simple application development become when all the business logic is encapsulated in an API. The unique responsibility of our AngularJS blog client is to display informations retrieved by the API (presentation layer).

The AngularJS application is fully independent of the API. It’s a HTML/JS/CSS app querying an API trough AJAX. It leaves in its own Git repository and is hosted on its own web server. As it only contains assets, it can even be hosted directly on a CDN such as Amazon CloudFront or Akamai.

To scaffold our AngularJS app we will use the official Angular generator of Yeoman. Install the generator then generate a client skeleton:

mkdir blog-client cd blog-client yo angular blog

Yeoman will ask some questions. We want to keep the app minimal:

don’t install Sass support

install Twitter Bootstrap (it’s optional but the app will look better)

uncheck all suggested angular modules

However, we will install Restangular, an awesome REST client for Angular that fit well with API Platform:

bower install --save lodash restangular

The Angular generator comes with the Grunt build tool. It compiles assets (minification, compression) and can serve the web app with an integrated development web server. Start it:

grunt serve

DunglasApiBundle provides a Restangular integration guide in its documentation. Once configured, Restangular will work with the JSON-LD/Hydra API like with any other standard REST API.

Then edit app/app.js file to register Restangular and configure it properly:

// app/scripts/app.js 'use strict'; /** * @ngdoc overview * @name blogApp * @description * # blogApp * * Main module of the application. */ angular .module('blogApp', ['restangular']) .config(['RestangularProvider', function (RestangularProvider) { // The URL of the API endpoint RestangularProvider.setBaseUrl('http://localhost:8000'); // JSON-LD @id support RestangularProvider.setRestangularFields({ id: '@id' }); RestangularProvider.setSelfLinkAbsoluteUrl(false); // Hydra collections support RestangularProvider.addResponseInterceptor(function (data, operation) { // Remove trailing slash to make Restangular working function populateHref(data) { if (data['@id']) { data.href = data['@id'].substring(1); } } // Populate href property for the collection populateHref(data); if ('getList' === operation) { var collectionResponse = data['hydra:member']; collectionResponse.metadata = {}; // Put metadata in a property of the collection angular.forEach(data, function (value, key) { if ('hydra:member' !== key) { collectionResponse.metadata[key] = value; } }); // Populate href property for all elements of the collection angular.forEach(collectionResponse, function (value) { populateHref(value); }); return collectionResponse; } return data; }); }]) ;

Be sure to change the base URL with the one of your API in production (protip: use grunt-ng-constant).

And here is the controller retrieving the posts list and allowing to create a new one:

// app/scripts/controllers/main.js 'use strict'; /** * @ngdoc function * @name blogApp.controller:MainCtrl * @description * # MainCtrl * Controller of the blogApp */ angular.module('blogApp') .controller('MainCtrl', function ($scope, Restangular) { var blogPostingApi = Restangular.all('blog_postings'); var peopleApi = Restangular.all('people'); function loadPosts() { blogPostingApi.getList().then(function (posts) { $scope.posts = posts; }); } loadPosts(); peopleApi.getList().then(function (people) { $scope.people = people; }); $scope.newPost = {}; $scope.success = false; $scope.errorTitle = false; $scope.errorDescription = false; $scope.createPost = function (form) { blogPostingApi.post($scope.newPost).then(function () { loadPosts(); $scope.success = true; $scope.errorTitle = false; $scope.errorDescription = false; $scope.newPost = {}; form.$setPristine(); }, function (response) { $scope.success = false; $scope.errorTitle = response.data['hydra:title']; $scope.errorDescription = response.data['hydra:description']; }); }; });

As you can see, querying the API with Restangular is easy and very intuitive. The library automatically issues HTTP requests to the server and hydrates “magic” JavaScript objects corresponding to JSON responses that can be manipulated to modify remote resources (trough PUT , POST and DELETE requests).

We also leverage the server side error handling to display beautiful messages when submitted data are invalid or when something goes wrong.

And the corresponding view looping over posts and displaying the form and errors:

<!-- app/views/main.html --> <!-- ... --> <article ng-repeat="post in posts" id="{{ post['@id'] }}" class="row marketing"> <h1>{{ post.name }}</h1> <h2>{{ post.headline }}</h2> <header> Date: {{ post.datePublished | date:'medium' }} <span ng-hide="post.isFamilyFriendly"> - <b>NSFW</b></span> </header> <p>{{ post.articleBody }}</p> <footer> Section: {{ post.articleSection }} </footer> </article> <form name="createPostForm" ng-submit="createPost(createPostForm)" class="row marketing"> <h1>Post a new article</h1> <div ng-show="success" class="alert alert-success" role="alert">Post published.</div> <div ng-show="errorTitle" class="alert alert-danger" role="alert"> <b>{{ errorTitle }}</b><br> {{ errorDescription }} </div> <div class="form-group"> <input ng-model="newPost.name" placeholder="Name" class="form-control"> </div> <div class="form-group"> <input ng-model="newPost.headline" placeholder="Headline" class="form-control"> </div> <div class="form-group"> <textarea ng-model="newPost.articleBody" placeholder="Body" class="form-control"></textarea> </div> <div class="form-group"> <label for="author">Author</label> <select ng-model="newPost.author" ng-options="person['@id'] as person.name for person in people" id="author"> </select> </div> <div class="form-group"> <input ng-model="newPost.datePublished" placeholder="Date published" class="form-control"> </div> <div class="form-group"> <input ng-model="newPost.articleSection" placeholder="Section" class="form-control"> </div> <div class="checkbox"> <label> <input type="checkbox" ng-model="newPost.isFamilyFriendly"> is family friendly? </label> </div> <button type="submit" class="btn btn-primary">Submit</button> </form>

It’s the end of this tutorial. Our blog client is ready and working! Remember of the old way of doing synchrone web apps? What do you think of this new approach? Easier and very much powerful isn’t it?

Of course there are some tasks to have a finished client including routing, pagination support and injecting raw JSON-LD in the generated HTML for search engines (use the response extractor hook provided by Restangular). As it’s only Angular tasks, it’s out of scope of this introduction to API Platform. But it’s a good exercise to add such features to the client. Feel free to share your snippets in comments.

Convinced of the facility of development and of the power of API Platform? Let us know your feedback in comments!

Roadmap

The first beta is ready and there is some task to do before the first stable release:

The code is almost ready and already used awesome compagnies including Les-Tilleuls.coop, Smile, the Trip Advisor group and ExaqtWorld. But API Platform still need to bullet proof. The first task before the release is: testing and hunting bugs .

. The documentation must be enhanced . This tutorial will be the base of the brand new doc being written. A dedicated website is also in progress.

. This tutorial will be the base of the brand new doc being written. A dedicated website is also in progress. We also need more integration (doc or libraries) with popular frontend technologies!

I hope to release the first stable version in July. You can help by contributing (bug reports, docs, code). Everything is hosted on GitHub and Pull Requests are very welcome.

Thanks

Special thanks to Samuel Roze (formerly Les-Tilleuls.coop and now Inviqa), Théo Fidry and Fabien Gasser (Smile), Maxime Steinhausser and Jérémy Derussé (SensioLabs) and Michaël Labrut (La Fourchette) for contributing code, docs and ton of good ideas.

API Platform would never have emerged without the great technologies and libraries it uses as foundation, especially Symfony, Doctrine JSON-LD, Hydra and Schema.org. A big thanks to all their contributors.

Commercial support

Feared of trying this new framework? Commercial support is already available: