Skip to content

Creating a ReST API

This section covers how to make a ReST API from scratch and consume it in any App (Web or Mobile).

If you're not familiar with ReST concepts, see this great explanation.

Prerequisites

First, you'll need a working setup of equal-framework on your localhost. See Getting started > Quick start (skip point 3. Defining classes)

If you look at the Directory structure, you'll get a good idea of what comes with every project. In this one, we'll use actions, classes, and data.

Each of those folders correspond to a specific role :

  • /actions = method DO (post, put/patch, delete, ...)
  • /data = method GET (fetching data from server)
  • /classes = class definition & DB architecture

Go ahead and create them in your package directory (ex: /packages/todolist/)

Defining classes

Inside folder /classes create a new PHP file for each class you want to declare.

To continue with our previous todolist-app example, we'll create two classes: Task and User.

/classes/Task.class.php
<?php
namespace todolist;
use equal\orm\Model;

class Task extends Model {

    public static function getColumns() {
        return [
            'title' => [
                'type' => 'string'
            ],
            'content' => [
                'type' => 'string'
            ],
            'deadline' => [ 
                'type' => 'datetime'
            ],
            'user_id' => [
                'type'           => 'many2one',
                'foreign_object' => 'todolist\User'
            ]
        ];
    }

}
/classes/User.class.php
<?php
namespace todolist;

class User extends \core\User {

    public static function getColumns() {
        return [
            'name' => [
                'type'              => 'string'
            ],
            'tasks_ids' => [
                'type'              => 'one2many',
                'foreign_object'    => 'todolist\Task',
                'foreign_field'     => 'user_id'
            ]
        ];
    }

}

We're done! A few things to consider :

  • In order to work, your parameters have to match with your front-end

  • Objects'IDs aren't needed here, they're automatically generated by eQual

  • As you can see we're using a DBMS relationship, linking user_id with tasks_ids. This is also handled by the framework, all you have to know is the definition syntax (see Basics > Objects parameters)

Defining GET

Open /data and create a new file for each GET method you want to use.

In the following example, we're asking our database to retrieve all tasks :

/data/tasks.php
<?php 
use todolist\Task;

list($params, $providers) = announce([
    'description'   => 'Retrieve the list of existing tasks',
    'params'        => [],
    'response'      => [
                        'content-type'  => 'application/json',
                        'charset'       => 'utf-8',
                        'accept-origin' => ['*']
                       ],
    'providers'     => ['context']
]);

list($context) = [ $providers['context'] ];

$list = Task::search([])
        ->read(['id', 'title', 'content', 'deadline', 'user_id' => ['id', 'name']])
        ->adapt('txt')
        ->get(true);

$context->httpResponse()
        ->body($list)
        ->send();
What it does :

announce() will handle the values of our query :

  • description
  • params gives additional requirements and conditions
  • response defines the format of the server response
  • providers helps us to access useful services such as context, orm, auth

We use list($context) = [$providers['context']] to implement the services we want to use

Then $list is where we receive the data from our query :

  • Task::search([]) searchs for data associated with Task

Function defined inside the Collection class

  • ->read([]) tells which parameters we want to retrieve from Task

  • ->adapt('txt') turns the data into strings

  • ->get(true)

Finally, $context is used to accomplish REST's purpose, displaying the data on our browser as JSON

<?php
$context
    ->httpResponse()// get the HTTP response being built
    ->body($list)   // populate the body with resulting list
    ->send();       // output the response (i.e. some plain 
                    // text @see https://www.w3.org/Protocols/rfc2616)

Using GET in your browser

We'll assume we already have an existing database containing a data sample of tasks, and have already done the following :

$> php run.php --do=test_package-consistency --package=todolist
$> php run.php --do=init_package --package=todolist

Open your browser, and in the localhost page you defined for eQual

To the address bar, add this :

?get=todolist_tasks

tasks refers to our tasks.php located in /data

It will display a JSON formatted answer showing an array of tasks

As easy as that. You now have a REST response that you can use in any frontend project

Defining DO

in /actions folder, create a sub-folder for each previously defined class, as follow :

/public
    /packages
        /todolist
            /actions
                /task
                /user
                ...
Go in the folder associated with the class you want to manipulate, and create a new file for each DO action you want to achieve (for more clarity, rename each file after their intended roles).

Now let's define the CREATE function of our API:

/actions/task/create.php
<?php
use todolist\Task;
use todolist\User;

list($params, $providers) = announce([
    'params' => [
        'title' => [
            'type'      => 'string',
            'required'  => true
        ],
        'content' => [
            'type'      => 'string',
            'required'  => true
        ],
        'deadline' => [
            'type'      => 'datetime',
            'default'   => date("Y-m-d H:i:s")
        ],
        'user_id' => [
            'type'      => 'integer',
            'required'  => true
        ]
    ],
    'response' => [
        'content-type'  => 'application/json',
        'charset'       => 'utf-8',
        'accept-origin' => ['http://localhost:4200', '*']
    ],    
    'providers' => ['context']
]);

list($context) = [$providers['context']];


$user = User::id($params['user_id'])->read(['id'])->first();

if(is_null($user)) {
    throw new Exception('user_not_found', EQ_ERROR_UNKNOWN_OBJECT);
}

$task = Task::create($params)
        ->read(['id', 'title', 'content', 'deadline', 'user_id' => ['id', 'name']])
        ->first(true);

$context->httpResponse()
        ->status(201)
        ->body($task)
        ->send();
What it does :

params defines the properties of our class Task (title, content, deadline, user_id). Some of them are declared with a required field which will make them mandatory (or not if "false", which is the default behavior)

$user is used to retrieve the ID of a single user, based on the field user_id

$task is then used with the ::create() method to initiate a POST request

Finally, we use $context to send it and get a REST response

Using DO in your browser

DO : CREATE
http://equal.local/index.php?do=todolist_task_create&title=my+task
&content=lorem+ipsum&user_id=1

eQual resolves the operation ?do=todolist_task_create to the script /todolist/actions/task/create.php

After CREATE, let's implement PUT and DELETE.

We'll do that with no further explanation as you should now be familiar with how it works

/actions/task/update.php
<?php 
use todolist\Task;
use todolist\User;

list($params, $providers) = announce([
    'params' => [
        'id' => [
            'type'      => 'integer', 
            'required'  => true
        ],  
        'title' => [
            'type'      => 'string',
            'required'  => true
        ],
        'content' => [
            'type'      => 'string',
            'required'  => true
        ],
        'deadline' => [
            'type'      => 'datetime',
            'default'   => date("Y-m-d H:i:s")
        ],
        'user_id' => [
            'type'      => 'integer',
            'required'  => true
        ]
    ],
    'response' => [
        'content-type'  => 'application/json',
        'charset'       => 'utf-8',
        'accept-origin' => ['http://localhost:4200', '*']
    ],    
    'providers' => ['context']
]);

list($context) = [$providers['context']];

$user = User::id($params['user_id'])->read(['id'])->first();

if(is_null($user)) {
    throw new Exception('user_not_found', EQ_ERROR_UNKNOWN_OBJECT);
}

$task = Task::ids($params['id'])
        ->update($params)
        ->read(['id', 'title', 'content', 'deadline', 'user_id' => ['id', 'name']])
        ->first(true);

$context->httpResponse()
        ->status(201)
        ->body($task)
        ->send();

In practice :

DO : PUT/PATCH
http://equal.local/index.php?do=todolist_task_update&id=1
  • id refers to the task we update
  • Additional parameters can be used, like title, content, dealine and user_id
/actions/task/delete.php
<?php 
use todolist\Task;
use todolist\User;

list($params, $providers) = announce([
    'params'        => [
        'id' => [
            'type' => 'integer',
            'required' => true
        ]
    ],
    'response'      => [
        'content-type'  => 'application/json',
        'charset'       => 'utf-8',
        'accept-origin' => ['http://localhost:4200', '*']
    ],
    'providers'     => ['context']
]);

list($context) = [$providers['context']];

Task::ids($params['id'])->delete(true);

$context->httpResponse()
        ->status(204)
        ->send();

In practice :

DO : DELETE
http://equal.local/index.php?do=todolist_task_delete&id=1

id= refers to the task we delete

Mapping the ReST API routes

Back to the root folder of equal installation, create a file: /config/routing/20-api_todolist.json

Replace everything with this :

{
    "/tasks": {
        "GET": {
            "description": "get all tasks",
            "operation": "?get=todolist_tasks"
        }
    },
    "/task": {  
        "POST": {
            "description": "get all tasks",
            "operation": "?do=todolist_task_create"
        }
    },
    "/task/:id" : {
        "PUT": {
            "description": "update a task",
            "operation": "?do=todolist_task_update"
        },
        "DELETE": {
            "description": "delete a task",
            "operation": "?do=todolist_task_delete"
        }           
    }      
}

What it does is pretty self-explanatory. The /:id is a way for us to target and retrieve a single task when needed.

From now on, route http://equal.local/tasks is equivalent to calling http://equal.local/?get=todolist_tasks.