Microrest.php: Generate A RESTful API On Top Of Any Relational Database, Powered By PHP And Silex

Jérôme Macias
Jérôme MaciasJanuary 05, 2015
#popular#php#rest#oss

After the introduction of ng-admin, we missed a simple way to serve a REST API based on tables stored in a relational database, like MySQL or PostgreSQL. The existing solutions (like FOSRestBundle for Symfony2) use a full stack framework, an ORM, serialization through annotations, many bundles, and lengthy configuration. We wanted something simpler. So we started from scratch, based only on API standards specifications.

The result of this work is called microrest.php. It helps you generate a basic REST API based on a YAML (in fact, RAML) description file. It's open-source, and ready for prime time.

What is a RESTful API ?

A RESTful API is an HTTP server for getting a list of resources, creating, updating, and deleting one single resource, based on a minimal specification. In fact the specification is the HTTP standard itself (see the HTTP RFC 2616 specifications). Actions are defined with HTTP method verbs: 'GET', 'POST', 'PUT', 'DELETE', and 'PATCH'. Responses are meant to be interpreted by a machine. They are generaly describe as JSON, following the JSON Schema specifications.

William Durand wrote a great introduction about RESTful API applications, we recommend it as a starting point.

What is RAML API description ?

How would you specify a REST API? The best answer is to look for standards. RESTful API Modeling Language (RAML) is a simple and succinct way of describing practically-RESTful APIs using YAML. It "encourages reuse, enables discovery and pattern-sharing, and aims for merit-based emergence of best practices".

A RAML file describes the request and response format of a REST API. It looks like this:

#%RAML 0.8
title: World Music API
baseUri: http://example.api.com/{version}
version: v1
traits:
  - paged:
    queryParameters:
      pages:
        description: The number of pages to return
        type: number
  - secured: !include http://raml-example.com/secured.yml
/songs:
  is: [paged, secured]
  get:
    queryParameters:
      genre:
        description: filter the songs by genre
  post:
  /{songId}:
    get:
      responses:
        200:
          body:
            application/json:
              schema: |
                { "$schema": "http://json-schema.org/schema",
                  "type": "object",
                  "description": "A canonical song",
                  "properties": {
                    "title": { "type": "string" },
                    "artist": { "type": "string" }
                  },
                  "required": [ "title", "artist" ]
                }
              application/xml:
                delete:
                  description: |
                    This method will *delete* an **individual song**

Introducing Microrest.php

Microrest.php is a Silex provider that generates a REST API based on a RAML description file. Since it's only a provider and not a full-blown application, it's easy to integrate with any PHP HTTP server, especially those compatible with the HttpKernelInterface. That means that it's compatible with Symfony2.

Microrest.php interacts with the database using the Doctrine DBAL, which is just a thin layer on top of PDO. No ORM, just a small compatibility layer, so that you can use it with MySQL, POstgreSQL, SQLite, even Oracle if you like.

It has minimum requirements, a very small footprint, is very easy to setup, and it will take care of controllers and database queries to make basic CRUD operations.

Installation

From an existing Silex application, adding REST API server capabilities is just a few steps away.

First, retrieve the library from packagist:

composer require marmelab/microrest "~1.0@dev"

If it's not already the case, you need to register DoctrineServiceProvider and ServiceControllerServiceProvider in the application:

$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
    'db.options' => array(
        'driver'   => 'pdo_sqlite', // or pdo_mysql, pdo_pgsql, pdo_*
        'path'     => __DIR__.'/app.db',
    ),
));

Note that the DoctrineServiceProvider only includes the Doctrine DBAL database abstraction layer, without the object-relational mapper (ORM) part.

This example uses a SQLite database, with a single posts table. Create it with the following commands:

sqlite3 app.db
sqlite> CREATE TABLE posts (id INTEGER PRIMARY KEY, title VARCHAR(255), body TEXT);

Define a RAML file describing the API for this posts table, for instance in an api.raml file:

#%RAML 0.8
title: microrest.php Example API
baseUri: http://localhost:8888/api
mediaType: application/json
/posts:
  displayName: Posts
  get:
    description: Retrieves one or more posts.
    responses:
      200:
  post:
    description: Creates one or more posts.
    responses:
      201:
  /{id}:
    get:
      description: Retrieves one or more posts.
      responses:
        200:
    put:
      description: Updates one or more posts.
      responses:
        204:
    delete:
      description: Deletes one or more posts.

Then, enable MicrorestServiceProvider:

$app->register(new Marmelab\Microrest\MicrorestServiceProvider(), array(
    'microrest.config_file' => __DIR__ . '/api.raml'
));

Please refer to the installation instruction for more details about configuration.

Start the server, and you're all set.

Usage

Check the new REST routes available in the top /api endpoint:

curl -X GET -i -H "Content-type: application/json" http://localhost:8888/api/
HTTP/1.1 200 OK
Content-Type: application/json
[
  "GET /posts",
  "POST /posts",
  "GET /posts/{id}",
  "PUT /posts/{id}",
  "DELETE /posts/{id}"
]

Using an API powered by microrest.php is just like using any Resftul API. For instance, create your first post by POSTing to /api/posts:

curl -i -X POST -H "Content-type: application/json" http://localhost:8888/api/posts -d '
    {
        "title": "My First Post",
        "body":"Really awesome !"
    }
    '
HTTP/1.1 201 Created
Content-Type: application/json
{
  "id": "1",
  "title": "My First Post",
  "body": "Really awesome !"
}

Retrieve the list of posts...

curl -X GET -i -H "Content-type: application/json" http://localhost:8888/api/posts
HTTP/1.1 200 OK
X-Total-Count: 1
Content-Type: application/json
[
  {
    "id": "1",
    "title": "My First Post",
    "body": "Really awesome !"
  }
]

...or a single post:

curl -X GET -i -H "Content-type: application/json" http://localhost:8888/api/posts/1
HTTP/1.1 200 OK
Content-Type: application/json
{
  "id": "1",
  "title": "My First Post",
  "body": "Really awesome !"
}

To update the post, use the HTTP PUT method:

curl -i -X PUT -H "Content-type: application/json" http://localhost:8888/api/posts/1 -d '
    {
        "title": "My First Post",
        "body":"Really, really awesome !"
    }
    '
HTTP/1.1 200 OK
Content-Type: application/json
{
  "id": "1",
  "title": "My First Post",
  "body": "Really, really awesome !"
}

Note that the PATCH method isn't supported yet.

To remove a post, use the DELETE method:

curl -X DELETE -i -H "Content-type: application/json" http://localhost:8888/api/posts/1
HTTP/1.1 204 No Content

The post with id 1 does not exists anymore, a GET call will return a HTTP 404 status:

curl -X GET -i -H "Content-type: application/json" http://localhost:8888/api/posts/1
HTTP/1.1 404 Not Found

Using ng-admin to Add a GUI To A RESTful API powered by microrest.php

After the API server generation, it's now easy to setup a backend admin thanks to ng-admin, another of our open-source projects.

Since ng-admin uses RestAngular to call the API, you must allow cross-domain HTTP requests using CORS.

There is a great Silex Provider for that: jdesrosiers/silex-cors-provider.

Install the Silex CORS provider with:

composer require jdesrosiers/silex-cors-provider "~0.1"

Configure CORS in the application script:

$app->register(new CorsServiceProvider(), array(
    'cors.allowOrigin' => 'http://localhost:8000', // your client application
    'cors.exposeHeaders' => 'X-Total-Count', // ng-admin read this header
));

Now, boostrap a new ng-admin application (see installation instructions) with the followig configuration, to be stored in a app.js file:

app.config(function(NgAdminConfigurationProvider, Application, Entity, Field) {
  function pagination(page, maxPerPage) {
    return {
      _start: (page - 1) * maxPerPage,
      _end: page * maxPerPage,
    };
  }

  var app = new Application("My Microrest application").baseApiUrl(
    "http://localhost:8888/api"
  );

  var post = new Entity("posts");

  app.addEntity(post);

  post
    .dashboardView()
    .title("Recent posts")
    .order(1)
    .limit(5)
    .pagination(pagination)
    .addField(new Field("title"));

  post
    .listView()
    .title("All posts")
    .pagination(pagination)
    .addField(new Field("id").label("ID"))
    .addField(new Field("title"))
    .listActions(["show", "edit", "delete"])
    .filterQuery(false);

  post
    .showView()
    .addField(new Field("id"))
    .addField(new Field("title"))
    .addField(new Field("body").type("wysiwyg"));

  post
    .creationView()
    .addField(new Field("title"))
    .addField(new Field("body").type("wysiwyg"));

  post
    .editionView()
    .title('Edit post "{{ entry.values.title }}"')
    .actions(["list", "show", "delete"])
    .addField(new Field("title"))
    .addField(new Field("body").type("wysiwyg"));

  NgAdminConfigurationProvider.configure(app);
});

You now have an AngularJS backend administration using the PHP Microrest API as a backend.

Microrest.php with ng-admin

Demo

The complete code for this demo API is available in the Microrest.php Silex provider repository.

To install it, it's just 3 commands:

git clone https://github.com/marmelab/microrest.php.git && cd microrest.php
make intall-demo
make run-demo
  • The API server will be available at http://localhost:8888/api/.
  • The admin backend will be avaible at http://localhost:8888/admin/.

What's Next ?

Microrest.php is already available on GitHub, under the MIT License.

We're really excited with the perspective of accelerating the development of microservices in PHP. It makes a lot of sense to use a smaller footprint for microservices, and Silex and Doctrine DBAL seem like ideal candidates for a REST API.

Microrest.php already works for basic REST APIs, but there is a lot more to do to be able to power professional web applications:

  • Generate a web documentation based on the RAML definition (think NelmioApiDocBundle for Silex).
  • Support a larger share of the RAML format (collection and collection item definition, as json-schema format, read the queryParameters definition, etc.)
  • Allow filtering on the list
  • Take care of cross-resource relationships (one-to-many, many to one, and many-to-many relationships)
  • Validate input on PUT and POST routes.
  • ...and many more

There are also many features that Microrest.php shouldn't implement, in order to stay... micro. If you need a non-standard mapping between the database schema and a non-REST API, then look for more elaborate solutions.

We'd love to get a hand on these tasks. So if you're interested, you can contribute to the project by forking Microrest.php repository and sending us a pull request.

Happy microresting!

Did you like this article? Share it!