Build a REST API with PHP and JsonSchema

Christoph Kappestein
5 min readFeb 13, 2016

--

This tutorial explains in detail how to build a REST API with PHP and JsonSchema. We will build a simple “todo” API where you can manage todo entries. At first we design the API. This step should be done independent of the language which you use to implement the API. For the implementation step we are using the PSX framework to develop the API.

Design

The first thing which you should do, when building an API, is to plan the design of the API. That means that you describe what endpoints are available and how the data of the request and response is structured. For our example we define two simple routes:

/todo
/todo/:todo_id

Normally the routes can be derived from your business model. I.e. if you have a contract model you could have a /contract and /contract/:contract_id route. Mostly you have a collection endpoint which returns or acts on multiple resources and a specific entity endpoint which works on a single resource.

Now we must also decide which HTTP methods we want allow for our endpoints. There are many interpretations when to use which method but a solid design would be:

/todo
GET: Return collection of entities
POST: Insert new entity to the collection
/todo/:todo_id
GET: Return an entity
PUT: Replace the content of the entity
DELETE: Remove the entity

If you plan to do partial updates on PUT you should use instead the PATCH
method. The PUT method requires you to send the complete changed entity where the PATCH method was designed for partial updates. Partial updates means that we send only the fields of the entity which we want to change and not the complete entity.

At last we have to define the request and response format of the endpoints. Therefor we use the popular JSONSchema format to describe the data. So we first model the todo entity (entity.json):

{
"title": "todo",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"insertDate": {
"type": "string",
"format": "date-time"
}
}
}

This would match to the following json payload:

{
"id": 1,
"title": "foo",
"insertDate": "2016–02–04T19:56:44Z"
}

The collection endpoint returns a list of entities. The schema of the collection (collection.json) could look like:

{
"title": "collection",
"type": "object",
"properties": {
"entry": {
"type": "array",
"items": {
"$ref": "file:///entity.json"
}
}
}
}

This would match to the following json payload:

{
"entry": [{
"id": 1,
"title": "foo",
"insertDate": "2016–02–04T19:56:44Z"
}]
}

And we define a success message (message.json) which is returned by the POST, PUT and DELETE method to indicate whether the operation was successful:

{
"title": "message",
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
}

This would match to the following json payload:

{
"success": true,
"message": "Insert todo entry was successful"
}

Now we have all needed schema definitions. We then assign the definitions to
the endpoints:

/todo
GET:
Response: collection.json
POST:
Request: entity.json
Response: message.json
/todo/:todo_id
GET:
Response: entity.json
PUT:
Request: entity.json
Response: message.json
DELETE:
Response: message.json

This completes the design phase. We have defined all available routes and
described the request and response format. As a side note this is also the time
where API specification formats (i.e. RAML, OpenAPI) come into play. They
provide a standard format for these information. But for our tutorial it is
enough to write them into a simple text document.

Implementation

In the implementation phase we want to turn the defined specification into
working code. Therefor we are using the PSX framework. We checkout the sample project with the following composer command:

composer create-project psx/sample psx

The psx/ folder should be inside the www/ directory of your web server. The
sample project provides already a basic directory structure for our project.

cache/  <- contains temporary and cache files
public/ <- directory which is accessible from the web
src/ <- source folder for the PHP code
tests/ <- folder which contains unit tests
vendor/ <- contains all external PHP dependencies

At first we have to adjust the configuration.php file. There we have to change
the key “psx_url” to the url which points to the public/ folder. In case the
psx folder is inside your www/ directory you would use i.e.: (http://127.0.0.1/psx/public). For production use it is recommended to create a vhost which directly points to the public/ folder so you only have to enter the domain name. If you visit the “psx_url” you should see an API response containing the text: “Welcome, this is a PSX sample application”.

Now we are ready to start developing our controllers. In the src/Api/ folder we create a new directory called Todo/. There we create a new file called Entity.php (src/Api/Todo/Entity.php) which will represent our entity endpoint.

Next we create a file called Collection.php (src/Api/Todo/Collection.php) which contains the code for our collection endpoint:

Then we need to add our schema files to PSX. Therefor we create a new folder structure (src/Resource/schema/todo/). There we copy the schema files (entity.json, collection.json, message.json) which we have designed. Instead of using plain JSONSchema files we could also use simple PHP classes containing specific annotations to describe the schema. Please take a look at the src/Model/ directory for an example.

At last we must assign the routes to the controllers. Open the file routes.php and enter the following route definitions at the top of the file:

[['ANY'], '/todo', App\Api\Todo\Collection::class],
[['ANY'], '/todo/:id', App\Api\Todo\Entity::class],

This tells PSX to route any request method to the specific controller. If you
visit now the collection (http://127.0.0.1/psx/public/index.php/todo) or entity (http://127.0.0.1/psx/public/index.php/todo/4) endpoint you should see the sample response. You can test the behaviour of the API with a REST tool by i.e. posting new todo entries to the endpoint.

Now we have an API which behaves exactly in the defined way. We only have to implement from which storage we CRUD the entites. Therefor you should use a service from the DI container. By default PSX provides a Doctrine DBAL connection service which you can use inside the controller to store the entities inside a database.

class Collection extends SchemaApiAbstract
{
/**
* @Inject
* @var \Doctrine\DBAL\Connection
*/
protected $connection;
}

But you are free to add any service you like to the DI container. To add a new sevice you have to add a new method to the DI container which is located at src/Dependency/Container.php. Additional business logic should also reside in a separate service which you then call inside the controller.

PSX provides also automatically a documentation about the available API endpoints (http://127.0.0.1/psx/public/documentation):

I hope this tutorial helps you with implementing a REST API in PHP. More
detailed information about the PSX framework at http://phpsx.org.

--

--

Christoph Kappestein
Christoph Kappestein

Written by Christoph Kappestein

I am a developer in the API space, currently working on Fusio an open source API management platform

No responses yet