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).

  • 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.


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 searches 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)

At this point, if you go to http://equal.local/index.php?get=todolist_tasks, you should see a JSON-formatted answer showing an empty array of tasks. To populate it, you can use the DO methods we will define in the next section.


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 the /actions folder, create a sub-folder for each previously defined class, as follows:

/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 its intended role.

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 set to 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, deadline, 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=1 refers to the task we delete.

Mapping the ReST API routes

Back to the root folder of the 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, the route http://equal.local/tasks is equivalent to calling http://equal.local/?get=todolist_tasks.