The previous day How to create REST API for Android app using PHP, Slim and MySQL – Day ½, we have learned fundamental concepts about REST API and preparing your development environment ready by installing the required tools needed. I hope everyone got good knowledge about REST and other technical areas. Also I am assuming that you got all the required tools installed.

Today we are going to learn how to setup a PHP project and writing the actual code for REST API. Also we’ll learn writing necessary SQL queries to perform database CRUD operations.

android rest api design using php mysql slim

8. Starting PHP Project

As we all know IDEs make development process easier. So I recommend you use an IDE for developing the PHP project instead of using plain notepad. You can go for Eclipse, Aptana Studio, PhpStorm or Netbeans. But I personally felt very comfortable using Netbeans for PHP projects.

PHP Project directory structure
The following diagram will give you an idea about the directory structure of the project which we are going to develop now.

task manager rest api php project directory structure

libs – All the third party libraries goes here. In our case we place Slim library here
include – All the helpers classes we build placed here
index.php – Takes care of all the API requests
.htaccess – Rules for url structure and other apache rules

Now let’s start the PHP project

1. Go to the directory where WAMP is installed. In general wamp will be installed in C:\wamp. (If you have installed any other software rather than WAMP, you should go to the directory recommended by that software).

2. As a first step we start with creating required directories. Inside wamp folder go to www folder (c:\wamp\www\) and create a folder named task_manager. This folder will be the parent directory of our project. Inside task_manager create two more folders named libs, include and v1.

3. Now the paste the Slim library inside libs folder. The download link for Slim is provided in previous part.

4. Normally Slim framework works when index.php includes in the url which makes url not well-formed. So using the .htacess rules we can get rid of index.php from the url and make some friendly urls. Inside v1 folder create a file named .htaccess and paste the following code. (Note that this file name shouldn’t include any additional extension in the name like .txt)

RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^(.*)$ %{ENV:BASE}index.php [QSA,L]

8.1 Preparing Helper Classes

First we start writing set of helper classes required in this project. These helper classes provides necessary functions required to interact with the database.

5. Inside include folder create file named Config.php with following content. This file contains the entire project configuration like database connection parameters and other variables.

<?php
/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'task_manager');

define('USER_CREATED_SUCCESSFULLY', 0);
define('USER_CREATE_FAILED', 1);
define('USER_ALREADY_EXISTED', 2);
?>

6. Create another class named DbConnect.php This class file mainly takes care of database connection.

<?php

/**
 * Handling database connection
 *
 * @author Ravi Tamada
 */
class DbConnect {

    private $conn;

    function __construct() {        
    }

    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . './Config.php';

        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);

        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
        }

        // returing connection resource
        return $this->conn;
    }

}

?>

Encrypting the password
7. The best way to secure the user passwords is not store them as plain text, instead all the passwords should be encrypted before storing in db. The following class takes care of encrypting the user password. Create another file named PassHash.php and paste the following code.

<?php

class PassHash {

    // blowfish
    private static $algo = '$2a';
    // cost parameter
    private static $cost = '$10';

    // mainly for internal use
    public static function unique_salt() {
        return substr(sha1(mt_rand()), 0, 22);
    }

    // this will be used to generate a hash
    public static function hash($password) {

        return crypt($password, self::$algo .
                self::$cost .
                '$' . self::unique_salt());
    }

    // this will be used to compare a password against a hash
    public static function check_password($hash, $password) {
        $full_salt = substr($hash, 0, 29);
        $new_hash = crypt($password, $full_salt);
        return ($hash == $new_hash);
    }

}

?>

8. Now create another class named DbHandler.php This class is one of the important files in our project which provides necessary functions to perform CRUD operations on the database. Every function is self explanatory by it’s name and comments, I don’t have to have to explain much about them.

<?php

/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 */
class DbHandler {

    private $conn;

    function __construct() {
        require_once dirname(__FILE__) . './DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }

    /* ------------- `users` table method ------------------ */

    /**
     * Creating new user
     * @param String $name User full name
     * @param String $email User login email id
     * @param String $password User login password
     */
    public function createUser($name, $email, $password) {
        require_once 'PassHash.php';
        $response = array();

        // First check if user already existed in db
        if (!$this->isUserExists($email)) {
            // Generating password hash
            $password_hash = PassHash::hash($password);

            // Generating API key
            $api_key = $this->generateApiKey();

            // insert query
            $stmt = $this->conn->prepare("INSERT INTO users(name, email, password_hash, api_key, status) values(?, ?, ?, ?, 1)");
            $stmt->bind_param("ssss", $name, $email, $password_hash, $api_key);

            $result = $stmt->execute();

            $stmt->close();

            // Check for successful insertion
            if ($result) {
                // User successfully inserted
                return USER_CREATED_SUCCESSFULLY;
            } else {
                // Failed to create user
                return USER_CREATE_FAILED;
            }
        } else {
            // User with same email already existed in the db
            return USER_ALREADY_EXISTED;
        }

        return $response;
    }

    /**
     * Checking user login
     * @param String $email User login email id
     * @param String $password User login password
     * @return boolean User login status success/fail
     */
    public function checkLogin($email, $password) {
        // fetching user by email
        $stmt = $this->conn->prepare("SELECT password_hash FROM users WHERE email = ?");

        $stmt->bind_param("s", $email);

        $stmt->execute();

        $stmt->bind_result($password_hash);

        $stmt->store_result();

        if ($stmt->num_rows > 0) {
            // Found user with the email
            // Now verify the password

            $stmt->fetch();

            $stmt->close();

            if (PassHash::check_password($password_hash, $password)) {
                // User password is correct
                return TRUE;
            } else {
                // user password is incorrect
                return FALSE;
            }
        } else {
            $stmt->close();

            // user not existed with the email
            return FALSE;
        }
    }

    /**
     * Checking for duplicate user by email address
     * @param String $email email to check in db
     * @return boolean
     */
    private function isUserExists($email) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE email = ?");
        $stmt->bind_param("s", $email);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }

    /**
     * Fetching user by email
     * @param String $email User email id
     */
    public function getUserByEmail($email) {
        $stmt = $this->conn->prepare("SELECT name, email, api_key, status, created_at FROM users WHERE email = ?");
        $stmt->bind_param("s", $email);
        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $user;
        } else {
            return NULL;
        }
    }

    /**
     * Fetching user api key
     * @param String $user_id user id primary key in user table
     */
    public function getApiKeyById($user_id) {
        $stmt = $this->conn->prepare("SELECT api_key FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        if ($stmt->execute()) {
            $api_key = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $api_key;
        } else {
            return NULL;
        }
    }

    /**
     * Fetching user id by api key
     * @param String $api_key user api key
     */
    public function getUserId($api_key) {
        $stmt = $this->conn->prepare("SELECT id FROM users WHERE api_key = ?");
        $stmt->bind_param("s", $api_key);
        if ($stmt->execute()) {
            $user_id = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $user_id;
        } else {
            return NULL;
        }
    }

    /**
     * Validating user api key
     * If the api key is there in db, it is a valid key
     * @param String $api_key user api key
     * @return boolean
     */
    public function isValidApiKey($api_key) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE api_key = ?");
        $stmt->bind_param("s", $api_key);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }

    /**
     * Generating random Unique MD5 String for user Api key
     */
    private function generateApiKey() {
        return md5(uniqid(rand(), true));
    }

    /* ------------- `tasks` table method ------------------ */

    /**
     * Creating new task
     * @param String $user_id user id to whom task belongs to
     * @param String $task task text
     */
    public function createTask($user_id, $task) {        
        $stmt = $this->conn->prepare("INSERT INTO tasks(task) VALUES(?)");
        $stmt->bind_param("s", $task);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            // task row created
            // now assign the task to user
            $new_task_id = $this->conn->insert_id;
            $res = $this->createUserTask($user_id, $new_task_id);
            if ($res) {
                // task created successfully
                return $new_task_id;
            } else {
                // task failed to create
                return NULL;
            }
        } else {
            // task failed to create
            return NULL;
        }
    }

    /**
     * Fetching single task
     * @param String $task_id id of the task
     */
    public function getTask($task_id, $user_id) {
        $stmt = $this->conn->prepare("SELECT t.id, t.task, t.status, t.created_at from tasks t, user_tasks ut WHERE t.id = ? AND ut.task_id = t.id AND ut.user_id = ?");
        $stmt->bind_param("ii", $task_id, $user_id);
        if ($stmt->execute()) {
            $task = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $task;
        } else {
            return NULL;
        }
    }

    /**
     * Fetching all user tasks
     * @param String $user_id id of the user
     */
    public function getAllUserTasks($user_id) {
        $stmt = $this->conn->prepare("SELECT t.* FROM tasks t, user_tasks ut WHERE t.id = ut.task_id AND ut.user_id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $tasks = $stmt->get_result();
        $stmt->close();
        return $tasks;
    }

    /**
     * Updating task
     * @param String $task_id id of the task
     * @param String $task task text
     * @param String $status task status
     */
    public function updateTask($user_id, $task_id, $task, $status) {
        $stmt = $this->conn->prepare("UPDATE tasks t, user_tasks ut set t.task = ?, t.status = ? WHERE t.id = ? AND t.id = ut.task_id AND ut.user_id = ?");
        $stmt->bind_param("siii", $task, $status, $task_id, $user_id);
        $stmt->execute();
        $num_affected_rows = $stmt->affected_rows;
        $stmt->close();
        return $num_affected_rows > 0;
    }

    /**
     * Deleting a task
     * @param String $task_id id of the task to delete
     */
    public function deleteTask($user_id, $task_id) {
        $stmt = $this->conn->prepare("DELETE t FROM tasks t, user_tasks ut WHERE t.id = ? AND ut.task_id = t.id AND ut.user_id = ?");
        $stmt->bind_param("ii", $task_id, $user_id);
        $stmt->execute();
        $num_affected_rows = $stmt->affected_rows;
        $stmt->close();
        return $num_affected_rows > 0;
    }

    /* ------------- `user_tasks` table method ------------------ */

    /**
     * Function to assign a task to user
     * @param String $user_id id of the user
     * @param String $task_id id of the task
     */
    public function createUserTask($user_id, $task_id) {
        $stmt = $this->conn->prepare("INSERT INTO user_tasks(user_id, task_id) values(?, ?)");
        $stmt->bind_param("ii", $user_id, $task_id);
        $result = $stmt->execute();
        $stmt->close();
        return $result;
    }

}

?>

8.2 Handling the API calls

Now we have all the required classes for the REST API. Now we can start the code to handle all individual api calls.

8. Inside v1 folder create a file named index.php and add the following code. Here we are including required libraries and other helper functions.

verifyRequiredParams() – This function verifies the mandatory parameters in the request.
validateEmail() – Verifies whether email address is valid one or not.
echoRespnse() – This function will echo the JSON response with a status code.

<?php

require_once '../include/DbHandler.php';
require_once '../include/PassHash.php';
require '.././libs/Slim/Slim.php';

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

// User id from db - Global Variable
$user_id = NULL;

/**
 * Verifying required params posted or not
 */
function verifyRequiredParams($required_fields) {
    $error = false;
    $error_fields = "";
    $request_params = array();
    $request_params = $_REQUEST;
    // Handling PUT request params
    if ($_SERVER['REQUEST_METHOD'] == 'PUT') {
        $app = \Slim\Slim::getInstance();
        parse_str($app->request()->getBody(), $request_params);
    }
    foreach ($required_fields as $field) {
        if (!isset($request_params[$field]) || strlen(trim($request_params[$field])) <= 0) {
            $error = true;
            $error_fields .= $field . ', ';
        }
    }

    if ($error) {
        // Required field(s) are missing or empty
        // echo error json and stop the app
        $response = array();
        $app = \Slim\Slim::getInstance();
        $response["error"] = true;
        $response["message"] = 'Required field(s) ' . substr($error_fields, 0, -2) . ' is missing or empty';
        echoRespnse(400, $response);
        $app->stop();
    }
}

/**
 * Validating email address
 */
function validateEmail($email) {
    $app = \Slim\Slim::getInstance();
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $response["error"] = true;
        $response["message"] = 'Email address is not valid';
        echoRespnse(400, $response);
        $app->stop();
    }
}

/**
 * Echoing json response to client
 * @param String $status_code Http response code
 * @param Int $response Json response
 */
function echoRespnse($status_code, $response) {
    $app = \Slim\Slim::getInstance();
    // Http response code
    $app->status($status_code);

    // setting response content type to json
    $app->contentType('application/json');

    echo json_encode($response);
}

$app->run();
?>

The JSON response

On calling every API request a JSON response will be issued with a HTTP status code. On the client side you have to verify the response http status code. If the status is 200, the request is processed successfully. Also you can notice a “error” node in the response. If the error value is true, that means some error occurred while processing the user data.

Api Calls without Authentication (without API key in the request header)

These calls don’t have to include Api Key in the request header. The main purpose of these calls is to interact with database without any authentication. User registration and login comes under this category.

⇒ User Registration

In order to interact with the API, the user has to register in our system first. Once he registered an API key will be generated and stored in the database. This API key will be private to that user only.

9. Add the following code in index.php. This function handles user registration.

/**
 * User Registration
 * url - /register
 * method - POST
 * params - name, email, password
 */
$app->post('/register', function() use ($app) {
            // check for required params
            verifyRequiredParams(array('name', 'email', 'password'));

            $response = array();

            // reading post params
            $name = $app->request->post('name');
            $email = $app->request->post('email');
            $password = $app->request->post('password');

            // validating email address
            validateEmail($email);

            $db = new DbHandler();
            $res = $db->createUser($name, $email, $password);

            if ($res == USER_CREATED_SUCCESSFULLY) {
                $response["error"] = false;
                $response["message"] = "You are successfully registered";
                echoRespnse(201, $response);
            } else if ($res == USER_CREATE_FAILED) {
                $response["error"] = true;
                $response["message"] = "Oops! An error occurred while registereing";
                echoRespnse(200, $response);
            } else if ($res == USER_ALREADY_EXISTED) {
                $response["error"] = true;
                $response["message"] = "Sorry, this email already existed";
                echoRespnse(200, $response);
            }
        });

In the following table you can find the API request information about the URL, HTTP method and the parameters needed to be posted.

URL /register
Method POST
Params name, email, password

Upon the successful registration the following json response will be issued.

{
    "error": false,
    "message": "You are successfully registered"
}

If the request is missing mandatory parameters the following json will be issued.

{
    "error": true,
    "message": "Required field(s) email, password is missing or empty"
}

⇒ User Login

10. Add the following code to handle user login. After verifying user credentials, the API Key for that user will be issued in the json response. The api key should be included in the request header in all remaining api calls.

/**
 * User Login
 * url - /login
 * method - POST
 * params - email, password
 */
$app->post('/login', function() use ($app) {
            // check for required params
            verifyRequiredParams(array('email', 'password'));

            // reading post params
            $email = $app->request()->post('email');
            $password = $app->request()->post('password');
            $response = array();

            $db = new DbHandler();
            // check for correct email and password
            if ($db->checkLogin($email, $password)) {
                // get the user by email
                $user = $db->getUserByEmail($email);

                if ($user != NULL) {
                    $response["error"] = false;
                    $response['name'] = $user['name'];
                    $response['email'] = $user['email'];
                    $response['apiKey'] = $user['api_key'];
                    $response['createdAt'] = $user['created_at'];
                } else {
                    // unknown error occurred
                    $response['error'] = true;
                    $response['message'] = "An error occurred. Please try again";
                }
            } else {
                // user credentials are wrong
                $response['error'] = true;
                $response['message'] = 'Login failed. Incorrect credentials';
            }

            echoRespnse(200, $response);
        });
URL /login
Method POST
Params email, password

On successful login the following json will be issued.

{
    "error": false,
    "name": "Ravi Tamada",
    "email": "ravi@gmail.com",
    "apiKey": "940bb12af8d7b040876f60f965c5be6d",
    "createdAt": "2014-01-07 23:38:35"
}

If the credentials are wrong, you can expect the following json.

{
    "error": true,
    "message": "Login failed. Incorrect credentials"
}

⇒ Verifying API Key

While dealing with task data, we need to identify the user using the API key in the request header by reading Authorization field. Basically we’ll look into database for matched API key and get the appropriate user. If the API key not present in users table, then we’ll stop the execution and echo the error json.

11. Add the following method in index.php. The method authenticate() will be executed every time before doing any task related operations on database.

/**
 * Adding Middle Layer to authenticate every request
 * Checking if the request has valid api key in the 'Authorization' header
 */
function authenticate(\Slim\Route $route) {
    // Getting request headers
    $headers = apache_request_headers();
    $response = array();
    $app = \Slim\Slim::getInstance();

    // Verifying Authorization Header
    if (isset($headers['Authorization'])) {
        $db = new DbHandler();

        // get the api key
        $api_key = $headers['Authorization'];
        // validating api key
        if (!$db->isValidApiKey($api_key)) {
            // api key is not present in users table
            $response["error"] = true;
            $response["message"] = "Access Denied. Invalid Api key";
            echoRespnse(401, $response);
            $app->stop();
        } else {
            global $user_id;
            // get user primary key id
            $user = $db->getUserId($api_key);
            if ($user != NULL)
                $user_id = $user["id"];
        }
    } else {
        // api key is missing in header
        $response["error"] = true;
        $response["message"] = "Api key is misssing";
        echoRespnse(400, $response);
        $app->stop();
    }
}

If the api key is missing in the request header, the following json will be echoed with 400 status code.

{
    "error": true,
    "message": "Api key is misssing"
}

If the api key is not valid following json will echoed with 401 status code.

{
    "error": true,
    "message": "Access Denied. Invalid Api key"
}

Api Calls with Authentication (Including API key in the request)
Following are the API calls should have an Api Key in the request header. These api calls primarily deals the user’s task data like creating, reading, updating and deleting.

⇒ Creating New Task

12. Add the follwing method to create a new task. Here you can notice that authenticate method is called to verify the Api key before inserting a new task.

/**
 * Creating new task in db
 * method POST
 * params - name
 * url - /tasks/
 */
$app->post('/tasks', 'authenticate', function() use ($app) {
            // check for required params
            verifyRequiredParams(array('task'));

            $response = array();
            $task = $app->request->post('task');

            global $user_id;
            $db = new DbHandler();

            // creating new task
            $task_id = $db->createTask($user_id, $task);

            if ($task_id != NULL) {
                $response["error"] = false;
                $response["message"] = "Task created successfully";
                $response["task_id"] = $task_id;
            } else {
                $response["error"] = true;
                $response["message"] = "Failed to create task. Please try again";
            }
            echoRespnse(201, $response);
        });
URL /tasks
Method POST
Params task

On successful creation of new task following json will be issued. If you got this json, you can see new row inserted in tasks and user_tasks tables.

{
    "error": false,
    "message": "Task created successfully",
    "task_id": 1
}

⇒ Getting All Tasks

13. Following method will list down all user’s tasks. We don’t have to submit any params for this api call.

/**
 * Listing all tasks of particual user
 * method GET
 * url /tasks          
 */
$app->get('/tasks', 'authenticate', function() {
            global $user_id;
            $response = array();
            $db = new DbHandler();

            // fetching all user tasks
            $result = $db->getAllUserTasks($user_id);

            $response["error"] = false;
            $response["tasks"] = array();

            // looping through result and preparing tasks array
            while ($task = $result->fetch_assoc()) {
                $tmp = array();
                $tmp["id"] = $task["id"];
                $tmp["task"] = $task["task"];
                $tmp["status"] = $task["status"];
                $tmp["createdAt"] = $task["created_at"];
                array_push($response["tasks"], $tmp);
            }

            echoRespnse(200, $response);
        });
URL /tasks
Method GET
Params

Following json will be issued for list of tasks. The “tasks” represents list of tasks as an array. Also if the “status” is 0, that means the task is not done yet.

{
    "error": false,
    "tasks": [
        {
            "id": 1,
            "task": "Complete REST article by Sunday",
            "status": 0,
            "createdAt": "2014-01-08 23:35:45"
        },
        {
            "id": 2,
            "task": "Book bus tickets!",
            "status": 0,
            "createdAt": "2014-01-08 23:56:52"
        }
    ]
}

⇒ Getting Single Task

14. Following method will fetch details of single task. You need to append the task id with a / to url. For an example if you want details of task 15, the url will be /tasks/15.

/**
 * Listing single task of particual user
 * method GET
 * url /tasks/:id
 * Will return 404 if the task doesn't belongs to user
 */
$app->get('/tasks/:id', 'authenticate', function($task_id) {
            global $user_id;
            $response = array();
            $db = new DbHandler();

            // fetch task
            $result = $db->getTask($task_id, $user_id);

            if ($result != NULL) {
                $response["error"] = false;
                $response["id"] = $result["id"];
                $response["task"] = $result["task"];
                $response["status"] = $result["status"];
                $response["createdAt"] = $result["created_at"];
                echoRespnse(200, $response);
            } else {
                $response["error"] = true;
                $response["message"] = "The requested resource doesn't exists";
                echoRespnse(404, $response);
            }
        });
URL /tasks/id (id should be replaced with task id)
Method GET
Params

The details of a single task will be in following json format.

{
    "error": false,
    "id": 2,
    "task": "Book bus tickets!",
    "status": 0,
    "createdAt": "2014-01-08 23:56:52"
}

If you pass a task id which is not there in the database, you will get 404 not found error.

⇒ Updating Task

15. Following code will take care of updating a task. The url for this api call is same as getting the details of single task, only difference is we should use PUT method instead of GET.

/**
 * Updating existing task
 * method PUT
 * params task, status
 * url - /tasks/:id
 */
$app->put('/tasks/:id', 'authenticate', function($task_id) use($app) {
            // check for required params
            verifyRequiredParams(array('task', 'status'));

            global $user_id;            
            $task = $app->request->put('task');
            $status = $app->request->put('status');

            $db = new DbHandler();
            $response = array();

            // updating task
            $result = $db->updateTask($user_id, $task_id, $task, $status);
            if ($result) {
                // task updated successfully
                $response["error"] = false;
                $response["message"] = "Task updated successfully";
            } else {
                // task failed to update
                $response["error"] = true;
                $response["message"] = "Task failed to update. Please try again!";
            }
            echoRespnse(200, $response);
        });
URL /tasks/id (id should be replaced with task id)
Method PUT
Params task, status (0 or 1)

Upon successful updation you will get following json.

{
    "error": false,
    "message": "Task updated successfully"
}

⇒ Deleting Task

16. Again delete task url is same as update task, but this requires DELETE method.

/**
 * Deleting task. Users can delete only their tasks
 * method DELETE
 * url /tasks
 */
$app->delete('/tasks/:id', 'authenticate', function($task_id) use($app) {
            global $user_id;

            $db = new DbHandler();
            $response = array();
            $result = $db->deleteTask($user_id, $task_id);
            if ($result) {
                // task deleted successfully
                $response["error"] = false;
                $response["message"] = "Task deleted succesfully";
            } else {
                // task failed to delete
                $response["error"] = true;
                $response["message"] = "Task failed to delete. Please try again!";
            }
            echoRespnse(200, $response);
        });
URL /tasks/id (id should be replaced with task id)
Method DELETE
Params

You will get following json if the task is deleted successfully.

{
    "error": false,
    "message": "Task deleted succesfully"
}

Here we completes the PHP and MySQL part. Now it’s time to move on to testing the API just to make sure that whatever code we have written is working.

Testing the API

Following is the list of URL we need to test using Chrome Advanced REST client extension with possible combinations of inputs.

URL Method Parameters Description
http://localhost/task_manager/v1/register POST name, email, password User registration
http://localhost/task_manager/v1/login POST email, password User login
http://localhost/task_manager/v1/tasks POST task To create new task
http://localhost/task_manager/v1/tasks GET Fetching all tasks
http://localhost/task_manager/v1/tasks/:id GET Fetching single task
http://localhost/task_manager/v1/tasks/:id PUT Updating single task
http://localhost/task_manager/v1/tasks/:id DELETE task, status Deleting single task

The following video shows you how to test the API thoroughly.

What’s Next?

Until now we have done a great job by developing fully functional REST services from scratch. But the services are limited to localhost. In other words your app can’t consume them if you want your app to be on playstore. So to make the services globally accessible, we need to host them on a public server. In the next part Android Hosting PHP, MySQL RESTful services to DigitalOcean, you will learn the complete process of buying the hosting space, deploying the services and finally mapping to a real domain URL.

Ravi is hardcore Android programmer and Android programming has been his passion since he compiled his first hello-world program. Solving real problems of Android developers through tutorials has always been interesting part for him.
  • Bla Bla Bla

    Ravi, does every user get a table named task created for them on the Database?

    • No, we have to create the table using the sql queries mentioned in the first part of this tutorial.

      • Bla Bla Bla

        I know we did create a table in the first part, but you know we have users. So, for every user, there’ll be a table Task for them autogenerated or it’s just that table task that’ll take care of it all. I’m actually building an application and finding it hard to use the REST cause, I’m having problem structuring the Database itself, cause I’m a novice when it comes to databases. Thank You.

        • Hi,

          There will be always one table tasks. For every user there won’t be a separate task table created.

          Every task is inserted as a row in task table. The relation between task row and user will be stored in user_tasks table where user_id and task_id will be stored.

          • Bla Bla Bla

            Ohw, now I dig. Very brilliant an idea. Thank You. And, we are waiting for the p3

  • xDroid

    I am working on Mac and using MAMP . I have created task_manager in my htdocs folder because there was no www folder there in Application/MAMP/ . I have created .htaccess file in root of task_manager and added all the code accordingly but I am not able to access my code on the server Its been started showing MAMP webpage but when I try to open that URL it just show 404!
    Please help me with MAC setup or what I am doing Wrong! 🙁

    • xDroid
    • John

      The .htaccess file should be in the “v1” directory, NOT in “task_manager”.
      This is clear from the image showing the project structure at the beginning of the article, but not in the text… The downloadable code archive has all files in correct locations…

      • Thanks John for the correction. Updated the article.

  • nasznjoka

    Hi Ravi thanks once again for this contribution to our droid life. I have this question if you can help me. What is the best way of turning a website which is not responsive into an android app? I ve been seeing parsing but i don’t exactly know how to work with it better and when you want to apply it to the website then its a disaster to me

    • Could you tell what do you mean by “turning website into android app”?

      • nasznjoka

        I mean to parse website content into an app for example like linkedin web looks just like their android app. So i want to know how to whether native or frameworks

        • That actually can be done using responsive page which adjusts to screen size. You have to design your page in responsive way.

          Normally nobody will do a parse and prepare a page on android app. You have to use webview for it.

          • siddhu

            WIthout responsive page nobody cant parse

          • Zaphod Beeblebroks

            what is exactly meaning of “responsive page” we are talking about here? How responsive is related with parsing html? There is some misunderstanding here..

          • nasznjoka

            Thanx man, but i just wish i could know more. I ve gone through the library in here but i have not seen such a tutorial

          • Zaphod Beeblebroks

            I think I understood a bit different his question.. Let’s say there is a news portal which get updated severl times per day but it doesn’t offer rss, json, gson or smth similar, but just html.. Let’s say there is a specific table of home page of this portal which list headlines. Is it possible to make web app (Google App Engine or similar platform) which will from time to time parse that home page, get the table and generate json file which will be exposed on specific request? That why we make android app, we get the json file and we display it in app interface and all “heavy processing of parsing” is done in the cloud. Additionally android app uses less internet data by fetching just newest json instead of whole html page source. What about that? What should be used for web app? Where to host it? 10q

          • nasznjoka

            Yes thats what i was asking for thank Zaphod for clarifying to make an app for a site but with different organization of menus, contents to the site itself

      • solomon

        waiting for 3rd part please make it 😀

    • shashi

      i m getting ,,,,,,”Required field(s) name, email, password is missing or empty” on register

  • Very nicely written code 🙂

  • siddhu

    Stunning!!!

  • Mat

    You are the best Ravi!! 🙂 Thank You

  • meirariel

    What does the Slim framework add other than JSON encoding? What code did we save writing by using it?

    • Hi Meirariel,

      JSON encoding is not the functionality of Slim framework. json_encode() is native function of PHP.

      Slim actually takes care of routing and the method of url $app->get(‘/route’) – . Also slim provides middle layer where we can execute “authenticate” method.

      If you go through this doc, you can find lot more options of slim.
      http://docs.slimframework.com/

      • meirariel

        Thanks. Your tutorials are so well thought out and clear. Much appreciated.

        • You are welcome and thanks for the appreciation 🙂

  • derpina

    Ravi, you are the best. Thank you for the article.

  • meirariel

    If I wanted to store the user’s picture on the cloud, together with their other account details, how would you recommend doing that?

  • tan hieu Huynh

    I try run Advanced REST client as demo but occur error 500 Internal Server Error, Pls help me fix it. Thank!

    • Guest

      are you using chrome?

  • babazumbula

    I’m also getting error 50. My Appache error log says:
    [Fri Jan 24 15:51:52 2014] [alert] [client 127.0.0.1] C:/wamp/www/task_manager/.htaccess: Invalid command ‘RewriteEngine’, perhaps misspelled or defined by a module not included in the server configuration

    but, still I dunno how to fix it

  • babazumbula

    Solved, I was missing line…
    So I added line
    LoadModule rewrite_module modules/mod_rewrite.so
    in my httpd.conf

  • Steve

    Love this. It will help me complete my project http://www.e-schedulebuilder.com. But my question is. Do I need oauth1.0a or 2.0 and an apikey for API protocols? I am designing software that will allow users to view there schedules, update employee information and stuff all from the mobile application.

  • Steve

    Nice tutorial!
    Any clue when the third part will be ready?

    • Hi Steve, I am facing some error in third part. I am currently solving it.

      • Steve

        Thanks for the quick reaction.
        By the end of this week?

        Thanks again for the great tutorials.

        • Yeah, sure.

          • Marat Duisenov

            thank you for your work, my friend. I’m looking forward third part

          • Sorry mrt sen,

            I seems like I can’t post the third part as I stopped by some hosting service issues.

          • Marat Duisenov

            It’s a thousand pities!

          • Rasit Aklar

            10,000!!!

          • zxzx

            any update on these issues?

  • Евгений Храмов

    When i try register new user i hav an error “Required field(s) …. is missing or empty”
    Then I was trying to check fields by print print_r($_REQUEST)
    In my case $_REQUEST is empty.

    • John

      Thank for the great write up Ravi – I’m also getting the same issue as above…. any ideas?

      • In the error message it will clearly shows you what are the parameters are missing while posting to that url.

        If it says “Required fields(s) name, email missing”, then you should add name=’your name’ and email=’youremail@gmai.com’ in the request parameters.

        Check the testing video to know how I added parameters while making the request.

        • Евгений Храмов

          I was checking parameters in chome/firefox plugins and Fiddrer2
          In my case all parameters was sended to server, byt on the server side is empty.

          • jujusous3

            same problem, any solution ?

    • Guest

      In Advance REST Client, the drop-down list of ‘Set “Content-Type” header to overwrite this value.’ leave it as “application/x-www-form-urlencoded”

  • shyam

    where is third part of your tutorial

    • andlab

      Im also really waiting for it :)))
      thanks anyway

  • San

    This is one of the great tutorial! Thank you very much Ravi!!!

  • Marcus

    Hi, i have downloaded the code and uploaded it to my webserver and using chrome to test as per the example but i receive a server 500 message stating that the Response does not contain any data.Any ideas why i would be receiving this message. Thanks

    • Might be some php errors. Check your server php / apache error log file.

    • Oscar Serna

      Hey Marcus!, I’m facing the same problem. How did you solve it? what kind of error did you find? was it a directory issue or definitly it was a code error?

      thank you!

  • Jao

    when will be the third?

    • Marat Duisenov

      He can’t post the third part as He stopped by some hosting service issues.

  • dev

    nice tutorial

    but i m getting ,,,,,,”Required field(s) name, email, password is missing or empty” on register

    • Евгений Храмов

      I posted the same issue 12 days ago and no luck.

      • Marat Duisenov

        this example works for me

        • Евгений Храмов

          Good for you!

          • Marat Duisenov

            I meant it works

          • Marat Duisenov

            Asking the right question is half the answer

    • meirariel

      Make sure you’re sending the required fields in the body of the request and not as headers.

      • ZiLang

        I got the same problem. (I posted the 3 parameters as shown in video in body). Any other possible reason ?

        • shanice

          In Advance REST Client, the drop-down list of “Set Content-Type header to overwrite this value.” leave it as “application/x-www-form-urlencoded”

  • akhil nair

    nice tutorial….waiting for last part………

  • dev

    can any one tell me ,how to solve ,,,,,,,,,,,,,,,,,

    Required field(s) name, email, password is missing or empty” on register

  • Hao Zhou

    This is very nice tutorial. But I have two questions:

    1. Why do you use Slim ? I found it’s difficult to debug when there is one more layer there in php code
    2. Is there a way to separate different functions into different php files instead of all functions in index.php? Because it’ll be very difficult to find the functions if there are too many functions in the API.

  • dr tarik

    thanks for all your effort your are realy one reference
    one Q : how to change content-type to application/xml

  • Guest

    How to get JSON from your php in eclipse?

  • Dhiren

    Kindly, Give me some knowledge for SLIP Framework.

  • Dhiren

    SLIM* Framework

  • akhil nair

    Nice Tut….. I’m looking forward third part

  • brokenOval

    Great article, thanks for the post. Keep getting a strange error on Ubuntu. I can make Slim work for something simple like “Hello world”, however when it tries to call the include files I get an internal server error 500 – checked the error log and got the following message whenever I try to run (for example the /register API): “PHP Fatal error: DbHandler::__construct(): Failed opening required ‘/var/www/include./DbConnect.php’ (include_path=’.:/usr/share/php:/usr/share/pear’) in /var/www/include/DbHandler.php on line 14”

    Anyone have an idea how to fix? Can confirm that Pear is installed on php and database is set up properly.

    • Jason Shen

      check your case as linux is case sensitie cheers

      • brokenOval

        Thanks Jason – all lowercase. I’m using an ubuntu distribution of apache if that helps. Any other ideas?

  • stephen

    amazing tutorial bro..keep up the good work

  • ape364

    Hi Ravi, good tutorial, thanks. can u tell why you need field ‘status’ in users table? i didnt find any use of this

  • Andrew

    Just wanted to say thank you! With minimum work on my end, I could just DL the code, change my DB config, and it worked, that easy! Read the Day 1 and Day2 of the article twice to get to know web services abit better too, but the video at the end really helped. Great job! For anyone having issues, just check your ReWrite module is working and edit the DB file, that’s it, great job!

  • zxzx

    So no 3rd part now?

  • neetu

    Your tutorial is great and very helpful.Can you post the third article if you not..then please tell me how to call rest webservice from android.I am so confused..

  • zxzx

    Any news about the 3rd part?

  • zxzx

    looks like the 3rd part ain’t coming.. 😉 thanx Ravi for all that u do

  • Hanimex

    Good tutorial. I ran through it on IIS8 (because I’m a masochist). I got to grips with the IIS URL Rewrite module and had to add in PUT and DELETE as allowed verbs for my PHP handler mapping but other than that it all worked. Thanks.

    • zxzx

      Can u plz post what u added Thx

      • Hanimex

        Well, for what it’s worth… I ended up with the following web.config file in my Task_Manager directory.

        Notes:
        1/ I created a “Task_Manager” application within a local website – the web.config changes were to that application only.
        2/ Instead of having to modify the .htaccess file that Apache uses, I had to use the URL Rewrite module (which sometimes isn’t installed by default on IIS) to create the “Task_ManagerApi Rule” above.
        3/ PHP is installed on *my* system using FastCGI – it might not be FastCGI on your setup and the .exe will be in a different location but, wherever it is, there will be a handler mapping for *.php files. The problem here is that IIS doesn’t allow PUT and DELETE verbs as default. I had to override this.
        4/ I used the IIS Manager to make the web.config changes above – I did not write the web.config file by hand (although you can if you are careful).

  • Shivendra Dubey

    Hi there i need your help, m constantly getting an error for as “Fatal error: Call to undefined method mysqli_stmt::get_result()” even though the mysqli and mysqlnd are enabled and running with latest php version. why the error is continue..

    • Shivendra Dubey

      please reply…

    • Douglas Roos

      I had the same issue but with GoDaddy hosting, So i’ve changed the $stmt->get_result()->$fetch_assoc() to $stmt->bind_result($id);

      while ($stmt->fetch()){

      $user_id = array(“id”=>$id);

      }

      And that’s it

      • Jason Shen

        Hi Gouglas
        just wondering how did you change your get task function to make it to work with bind_result instead of get_result, i am like stuck on that at the moment any help would be awesome cheers
        jason

  • Hoàng Bảo Dương Hùng

    Hi,
    I want upload image via rest api for android using php slim :(. Please guide for me.

  • gnuManiac

    Hi,
    great job but i need your help.
    For me “getting all tasks” it’s broken (500 Internal error – Response does not contain any data).

    Getting single task with no problems.

    • gnuManiac

      get_result() failure.
      Solved installing php5-mysqlnd

  • Muhammad Awais

    Mannnn !!! u r Awesome !!!! Thanksss alot for restful api tutorial.. :))) carry on

  • Guest

    Hi,

    Thanks for the Tutorial. I am having an issue when i Register an new user. I modified the parameters but i get the following message when i execute the request, “Response does not contain any data.”

    Could you kindly help me out. Thanks

  • Craig Daly

    Is anyone getting an ‘500 Internal Server Error’ message while using the Advanced Rest Client? I’m using MAMP on a Mac running OS X 10.9.2 Mavericks. Great tutorial, btw!!

    • Eugene Kiver

      You shoul’ve check your wamp directory for apache logs, I found the error desciption and googled for solution, in wamp you can click on it in windows bottom tray (or how they all it) then go to Apache->Apache Modules->Rewrite module. It turns out that it is disabled by default.

  • Craig Daly

    @walox checked the httpd.conf file & everything looks fine. here’s my output:

    Status
    500 Internal Server Error Loading time: 35
    Request headers
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.4 Safari/537.36
    Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
    Authorization: bf45c093e542f057caee68c47787e7d6
    Content-Type: application/x-www-form-urlencoded
    Accept: */*
    DNT: 1
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: en-US,en;q=0.8
    Cookie: SQLiteManager_currentLangue=2

    Response headers
    Date: Sun, 30 Mar 2014 19:56:22 GMT
    Server: Apache/2.2.26 (Unix) mod_fastcgi/2.4.6 mod_wsgi/3.4 Python/2.7.6 PHP/5.5.10 mod_ssl/2.2.26 OpenSSL/0.9.8y DAV/2 mod_perl/2.0.8 Perl/v5.18.0
    X-Powered-By: PHP/5.5.10
    Content-Length: 1980
    Connection: close
    Content-Type: text/html

    and here’s the response callback:
    Slim Application Error
    #1 /Applications/MAMP/htdocs/manage_tasks/include/DbConnect.php(31): DbConnect::connect()
    #2 /Applications/MAMP/htdocs/manage_tasks/include/DbHandler.php(19): DbConnect->connect()
    #3 /Applications/MAMP/htdocs/manage_tasks/v1/index.php(74): DbHandler->__construct()
    #4 [internal function]: {closure}()
    #5 /Applications/MAMP/htdocs/manage_tasks/libs/Slim/Route.php(436): call_user_func_array(Object(Closure), Array)
    #6 /Applications/MAMP/htdocs/manage_tasks/libs/Slim/Slim.php(1307): SlimRoute->dispatch()
    #7 /Applications/MAMP/htdocs/manage_tasks/libs/Slim/Middleware/Flash.php(85): SlimSlim->call()
    #8 /Applications/MAMP/htdocs/manage_tasks/libs/Slim/Middleware/MethodOverride.php(92): SlimMiddlewareFlash->call()
    #9 /Applications/MAMP/htdocs/manage_tasks/libs/Slim/Middleware/PrettyExceptions.php(67): SlimMiddlewareMethodOverride->call()
    #10 /Applications/MAMP/htdocs/manage_tasks/libs/Slim/Slim.php(1254): SlimMiddlewarePrettyExceptions->call()
    #11 /Applications/MAMP/htdocs/manage_tasks/v1/index.php(339): SlimSlim->run()
    #12 {main}

    I can’t tell if it’s a php coding error based in the 1st 2 lines of the response callback or if it’s an MAMP issue.

  • Mert Aksoy

    PHP Fatal error: Call to undefined function apache_request_headers() in /public_html/task_manager/v1/index.php on line 20

    added this function:

    if(!function_exists(‘apache_request_headers’)) {
    function apache_request_headers() {

    $headers = array();

    foreach($_SERVER as $key => $value) {

    if(substr($key, 0, 5) == ‘HTTP_’) {

    $headers[str_replace(‘ ‘, ‘-‘, ucwords(str_replace(‘_’, ‘ ‘, strtolower(substr($key, 5)))))] = $value;

    }else if($key == “Authorization”){

    $headers[$key] = $value;

    }

    }

    return $headers;

    }

    }

    now i couldnt see the value authorization in headers

    • Jesús Expósito

      Any fix to this issue? I use a similar function but the result is the same. Can’t see the auth header.

      if(!function_exists(‘apache_request_headers’)){

      function apache_request_headers(){

      static $arh = array();

      if (!$arh){

      $rx_http = ‘/AHTTP_/’;

      foreach ($_SERVER as $key => $val){

      if(preg_match($rx_http, $key)){

      $arh_key = preg_replace($rx_http, ”, $key);

      $rx_matches = array();

      // do some nasty string manipulations to restore the original letter case

      // this should work in most cases

      $rx_matches = explode(‘_’, $arh_key);

      if( count($rx_matches) > 0 and strlen($arh_key) > 2 ){

      foreach($rx_matches as $ak_key => $ak_val)

      {

      $rx_matches[$ak_key] = ucfirst($ak_val);

      }

      $arh_key = implode(‘-‘, $rx_matches);

      }

      $arh[$arh_key] = $val;

      }

      }

      }

      return $arh;

      }

      }

      • dfdsafsdds

        what r u doing bro 🙂

    • Brendan

      Ravi,

      A million thanks for this – it is easily the best example on the net of an authorization based restful api for PHP.

      Also it is brilliantly presented and coded – big kudos to you!!!

      Hi – I’ve just got this working on PHP 5.3 install – where the apache_request_headers does not exist.

      You need to follow up with two changes.

      1. Update your .htaccess file with

      RewriteRule .* – [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

      This will add the variable HTTP_AUTHORIZATION such that $_Server will report on it.

      2. In the replacement function – the process to produce first character uppercase needs changed

      From:
      $rx_matches[$ak_key] = ucfirst($ak_val);

      To:
      $rx_matches[$ak_key] = ucfirst(strtolower($ak_val));

      Basically the function ucfirst does not work if the variable is already uppercase.

      Do both of these changes and voila – Authorization is presented and the scripts work.

  • Hi there are 2 errors in the DbHandler.php and DbConnect php files. You need to remove the “.” in the “./DbHandler” and “./Config.php” expression at lines 15.

  • Dũng

    how to register direct on url
    example api other: domain/Servers/Login/password:12z/username:12z

    how to use in your project.
    please help me.

  • endder

    Any news about the 3rd part?

  • jpgrassi

    Great post, thanks!

    I have only one question, all your methods use the parameters in the body. What changes do I need to make to accept json as parameters instead of body parameters?

    • schlocke

      I believe all request have a body, so instead of:

      parse_str($app->request()->getBody(), $request_params);

      you would do:

      $array = array();
      $array = json_decode($app->request()->getBody());

  • Guest

    thanks for all your effort man, I have a problem in the file DbHandler.php exactly in the line 221:

    $new_task_id = $this->conn->insert_id;

    where is defined insert_id ?

    thanks.

  • meka troniko

    muchas gracias!

  • Kalpesh Lodha

    Very Nice Tutorial….
    Thanks for your efforts….

  • villdre

    Hi – thank for the amazing & detailed tutorial … I am using MAMP on Mac (rather than WAMP on Windows), and have tested that my server is working fine. I have also followed all the steps in this tutorial. But I continuously get a 404 error when trying the POST method (or any other method) as per above. I am using the Chrome Advanced Rest Client and trying to access the following URI: http://localhost:8888/task_manager/v1/register

    (My server is on port 8888).
    Any hints on why I might be getting 404 would be greatly appreciated.
    Thanks in advance (and apologies for posting this as a bug earlier).

    • Eugene Kiver

      Just put in this folder some other .php file and check whether this path is acessible. If yes. Then you might be having the problem of php beginners who don’t know that $app->run(); should go AFTER the methods like $app->get(… So just download the source rename it to .zip and see how v1/index.php should look like, or copy’n’paste all the methods after the app->run.

      • villdre

        Thanks Eugene – I will try your suggestions.

    • Eugene Kiver

      And one more thing, for WAMP’s default settings include empty password field. While MAMP’s pass is “root”, so you have to edit line 6 in include/Config.php to “define(‘DB_PASSWORD’, ‘root’);” and yes you should include port 8888 in your chorme REST call

    • Zak

      I had the same problem, and apparently when I pasted the code for the functions that require authentication, I did that after $app->run() in index.php. Make sure you paste those functions that require authentication BEFORE $app->run();

  • Michael

    thanks allot

  • Eugene Kiver

    Can you update the source code? When I try to download it, I get a 66k file named Android without extension

    • Eugene Kiver

      manually adding .zip extension solved the problem

  • Classic example for beginners. Thank you very much.

    We are Test using Chrome Advanced REST client extension. Can anyone explain or PUT code to test with-in php file. (May be it was so easy but for beginners it good to find here)

  • Diego

    Hi, do you have an example for calling the api with ajax?

  • Diego

    Sorry, here are test lines:

    var dataTest = { “name”: “test”, “email”: “pepe@peperoni.com”, “password”: “sarasa” }
    var urlAjax = “http://localhost:8080/RSApi/v1/register”;

    $.ajax({
    type: “POST”,
    url: urlAjax,
    contentType: “application/json”,
    data: dataTest ,
    success: function(data) { alert(“ajax worked”); },
    error: function(data) {console.log(data); },
    dataType: ‘json’
    });

    I tried sending it as json object but with no success.

  • Guest

    Hello,
    Thank you very much for this tutorial.

    Can you please tell me how to return the logged in user (in android application) information by this url $app->get(‘/me’.

    Thank you.

  • Guest

    “500 Internal Server Error”

    • hakim

      i have the same probleme ^^

    • Bibash Shah

      If you are copying code exactly check once. I had same problem and what I was mistaken was the Config.php instead i had made config.php.

  • Gourav shrivaspati

    Help me to create data base for this ….

  • Bala Vishnu

    Hello, great tutorial. It works fine in my localhost using the WAMP server but it dosen’t work in my APACHE server….

  • Brendan

    Ravi,

    A million thanks for this – it is easily the best example on the net of an authorization based restful api for PHP.

    Also it is brilliantly presented and coded – big kudos to you!!!

    To all of the people struggling with apache_request_headers_not working – I’ve just got this working on PHP 5.3 install – where the apache_request_headers does not exist.

    You need to follow up with two changes.

    1. Update your .htaccess file with

    RewriteRule .* – [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

    This will add the variable HTTP_AUTHORIZATION such that $_Server will report on it.

    2. In the replacement function – the process to produce first character uppercase needs changed

    From:
    $rx_matches[$ak_key] = ucfirst($ak_val);

    To:
    $rx_matches[$ak_key] = ucfirst(strtolower($ak_val));

    Basically the function ucfirst does not work if the variable is already uppercase.

    Do both of these changes and voila – Authorization is presented and the scripts work.

    • gkm

      Good work for pointing that out but I would like to ask …. How is the api_key supposed to be sent as part of the headers …..How do i include it in the request that I’m making??? Thanks

    • Yakup Başer

      i get an error on tht function:
      Call to undefined method mysqli_stmt::get_result() in /home1/beysplay/public_html/task_manager/include/DbHandler.php on line 268

      public function getAllUserTasks($user_id) {

      $stmt = $this->conn->prepare(“SELECT t.* FROM tasks t, user_tasks ut WHERE t.id = ut.task_id AND ut.user_id = ?”);

      $stmt->bind_param(“i”, $user_id);

      $stmt->execute();

      $tasks = $stmt->get_result(); <<<close();

      return $tasks;

      }

  • ozzner

    have problems
    500 and 404

  • ozzner

    wamp server 2.2d

  • Bilal Inamdar

    500 problem
    Solution : turn on rewrite module in apache

    • Miguel Lozano

      ty

  • Bilal Inamdar

    You have create a very good tutorial… I do not say so easily… Please include a Login system scripts in PHP for android and normal web browser. Because this is the only piece remaining in your master piece

  • Tom Lawton

    Thanks for a great tutorial. I was having a bit of trouble with 404 errors in the beginning and I needed to modify the rewrite rule to, probably due to the host I was using. Hopefully this will help others having the same issue:

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ /task_manager/v1/index.php [QSA,L]

    • andrick

      I think that’s because the author forgot to mention where he defined %{ENV:BASE}

    • Harish Sn

      can u tell me how to write rewrite rule for subdomains?

  • Joey Salac Hipolito

    Uhmm, this will not yet accept cross-origin requests right?

  • ozzner

    helpme please ;(

    404 Page Not Found

    The page you are looking for could not be
    found. Check the address bar to ensure your URL is spelled correctly. If
    all else fails, you can visit our home page at the link below.

    Visit the Home Page

    • basheer

      have checked it with the chrome extension, try it out, it works

  • Paco

    Edited. Everything OK.

  • Ron

    For some reason I cannot download the source of signup (no email received yet). Can somebody zip the entire working project and share it? My son asked me for a good tutorial ans this one looks very good. Thanks in advance

    Ron

    • ian

      That happened with me as well. Just check ur spam email, thats where mine went into

    • DeveloperH

      The tutorial is so clear just flow it, you don’t need the ZIP

  • DeveloperH

    – You need to inizialize $password_hash in the DbHandler Class at line 79
    – Don’t put the require in the constructor, it’s better in the beginning of the class just before
    – Delete the ‘.’ in the require when you include Config.php and DbConnect.php

  • Tim

    Thank you for the tutorial but …

    I do not understand why I get a 500 Internal Server Error :

    – I unzip the archive
    – I placed the folder task_manager on www folder (c:wampwww)
    – I turned on rewrite module in apache
    – I uncommented the line “LoadModule rewrite_module modules/mod_rewrite.so” on C:wampbinapacheapache2.4.9confhttpd.conf
    – always this error 500 Internal Server Error…

    • Mohammed

      Verify your Apache Modules in Wamp Server and Enable the rewrite_module

      • adnan

        make sure to enable the rewrite_module in the php.init. Search google to get full details.

  • Tom

    Thank you for the tutorial but I have some problems

    I have a 500 Internal Server Error, I turned on the module in apache rewrite but I still have this error

    sugestions?

  • Could we use Slim’s http://docs.slimframework.com/#Middleware-Overview (Middleware) technique to authenticate users? I am new to Slim and just trying to have it’s most.

  • Dennis Hatcher

    Great tutorial! I ran into a couple of issues:

    1. needed to change
    include_once dirname(__FILE__) . ‘./Config.php’;
    To:
    include_once dirname(__FILE__) . ‘/Config.php’;
    in DbConnect.php (dropped the dot (.) before the file name). Might have something to do with being in a hosted environment.

    2. Had issues with DbHandler.php. I have mysqlnd, but only the PDO extension (again, hosted environment). All calls using $stmt->get_result()->fetch_assoc(); needed to be re-written. For example the getUserByEmail:

    public function getUserByEmail($email) {
    $stmt = $this->conn->prepare(“SELECT name, email, api_key, status, created_at FROM users WHERE email = ?”);
    $stmt->bind_param(“s”, $email);
    if ($stmt->execute()) {
    $stmt->bind_result($name, $email, $api_key, $status, $created_at);

    /* fetch values */
    mysqli_stmt_fetch($stmt);

    /* set values */
    $user[‘name’] = $name;
    $user[’email’] = $email;
    $user[‘api_key’] = $api_key;
    $user[‘created_at’] = $created_at;

    $stmt->close();
    return $user;
    } else {
    return NULL;
    }
    }

    Thanks again!

    • mannan

      Surerb. Good Alteration. Timely re-written

    • gkm

      Thanks a lot Dennis good point out.

    • Yakup Başer

      right there
      i get an error on tht function:

      Call to undefined method mysqli_stmt::get_result() in /home1/beysplay/public_html/task_manager/include/DbHandler.php on line 268

      public function getAllUserTasks($user_id) {

      $stmt = $this->conn->prepare(“SELECT t.* FROM tasks t, user_tasks ut WHERE t.id = ut.task_id AND ut.user_id = ?”);

      $stmt->bind_param(“i”, $user_id);

      $stmt->execute();

      $tasks = $stmt->get_result(); <<<close();

      return $tasks;

      }

  • Prateek

    Where is android in this tutorial? This is about a general php server exposing REST APIs..

    • This part explains server side technology when your app is connecting to a cloud hosting server.

  • ALLAN

    This tutorial is Great,!, really helpful..

  • Donny Crash

    Hi there, I really enjoyed this tutorial.. I’ve made some mods to accept JSON / Form Data and for it to work on php 5.4. Do you mind if I post it on github?

    • Yeah you can post. But it will be much appreciated if you can give link back to my blog.

  • ssougnez

    Hi… Awesome tutorial once again. The next step in my app is to use Facebook API. After a small search on Internet, I directly found an article on this website. This is really great.

  • Robin Tyglare

    Hi. Thanks for a very good tutorial. Made me learn alot. But, I would like to send the requests via application:json instead of application:x-www-form-urlencoded, but I just cannot make it work. Please give me a push in the right direction 🙂 I’m realy stuck here. Anyone help?

    • Chandan Singh

      just used

      $app->request->getBody();

      to extract ur json instead of

      $app->request->post(param);

  • Florian Kirschna

    Very nice tutorial, but I’m missing the point where you secure your registration method. I mean it would be really easy for a spammer to take advantage of this, or am I missing something here ? What is the industry standard for this ?

  • Raja

    Hi . Thanks for the tutorial.

    Why do I keep getting “Unexpected token C” -> JSON Tab when testing with the Advance Client Rest ?

    However I can see the registered succesful message in the Raw tab.

    Thanks

    • Adnan

      This happens if you’re echoing something in the script. Remove all echos.

      • Guest

        I have the same problem and the only echo in the index.php script is this

        echo json_encode($response);

        which i think is the one that should be there (since i’m using the download copy of the project). does any body knows if there is something else to review?

  • Adnan

    Ravi,

    Amazing tutorial. Helped me understand REST services clearly.

    One part of the code I don’t understand though:

    $new_task_id = $this->conn->insert_id;

    What is this insert_id? Is it a variable, function, where did it come from?

    • Adnan

      Another question, do we need the relation table? can’t we achieve the same result by adding a FK column in the ‘tasks’ table?

      Thanks again

    • The is the mysql row id (primary auto increment value) of last inserted row. i.e the current row that is you just inserted.

  • Amazing Tutorial, Now I came to understand that how to integrate Slims library to develop my PHP projects, Would like to know whether this help in android app development. Also I learnt interesting things here http://bit.ly/1dhMAqu
    http://bit.ly/1mgMn8D

  • avinash kumawat

    Great job ravi its help alot 🙂 but got 404 error code rewrite module is enable already , don’t know why this give 404, plz help me out

  • Rendy Setya Pratama

    Hello Ravi,
    btw thanks for stunning tutorial !

    I have one problem when test the API in google chrome.

    The problem is :

    Fatal error : Call to a member function bind_param() on a non-object in C:xampphtdocstask_managerincludeDbHandler.php on line 113

    Can you help me ?

    Thanks 🙂

      • Rendy Setya Pratama

        Thanks for another response 🙂

        btw, you say that you do not continue part 3, actually what the contents of the tutorial in part 3?
        because I’m developing a restful API for my project and Your Tutorial help me a lot.

        I just feeling curious.

        Thanks 🙂

        • Third part is about hosting the code on a real server like bluehost, hostgator. I wanted to explain how to buy a server, host the files, configure the server and expose the services to public. But after finalizing everything I got a problem in verification. That is why I stopped posting that part.

          • Rendy Setya Pratama

            Thanks for your fast and friendly response 🙂

            I have a last question for you, may I ?

            I told you before that I’m develope a RESTful API for my project right now.
            Mobile as client and Website as server.

            But I don’t know how to make the RESTful API (from your tutorial) work with Mobile (as Client) and Website (as Server).

            Can you tell me the steps ? (only the steps not the code)

            Your answers really help me.

            Thanks :).

  • lightingwang

    Hello Ravi,
    thx for your tutorial!!
    I want use it to transfer data with android device.
    But i am just a beginner..
    if you are not busy, Can you make another tutorial about how to use android device to connect to restful server via php, json and mysql…
    I try it but it is so hard T.T
    Thanks~~~~

    • Chandan Singh

      if you want to extract json just use

      $app->request->getBody();

      instead of

      $app->request->post(param);

    • narendra nauth

      There is a great tutorial on Lynda.com for this

  • Michael

    Hi,

    Good tutorial, thank you very much Ravi.

    I try it on WAMP and on LAMP and on two of us, I’ve always got the 400 Bad Request error but I try the same thing you. I don’t understand.

    My test:
    http://localhost/task_manager/v1/register
    Method: Post
    No header
    name: Michael
    email: michael@test.com
    password: chocolate

    And I’ve got this:
    {
    error: true
    message: “Required field(s) name, email, password is missing or empty”
    }

    I’m really discomfit, and I see nothing in log that could help me.

    Finally I succeed to register one user in database (manually) and try to login with this user with api key etc.. but I still have the same issue (Bad request: “…email, password is missing or empty”)

    info => I think the api key is well recognized because if I remove it I’ve got the message: “api key is missing”

    If someone had the same problems or hase some solutions to offer me, I will be grateful to him for helping me.

    Thank you. Bye

    • mani

      i am getting the same thing, did you fix it? does it work now?

      • narendra nauth

        I dont believe you are passing the params correctly. Attach the exact code you are using so assistance can be provided

        • brokenOval

          Also getting the same problem my side, the code as follows:

          $app->post(‘/test’, function() use ($app) {
          // check for required params
          verifyRequiredParams(array(’email’));
          echo “Success”;
          });

          And the verifiyRequired params is unchanged from the original:

          function verifyRequiredParams($required_fields) {
          $error = false;
          $error_fields = “”;
          $request_params = array();
          $request_params = $_REQUEST;
          // Handling PUT request params
          if ($_SERVER[‘REQUEST_METHOD’] == ‘PUT’) {
          $app = SlimSlim::getInstance();
          parse_str($app->request()->getBody(), $request_params);
          }
          foreach ($required_fields as $field) {
          if (!isset($request_params[$field]) || strlen(trim($request_params[$field])) stop();
          }
          }

          It seems to be breaking on the line $request_params = $_REQUEST; where no data is being passed from the REST params

          • narendra nauth

            Try this:
            ‘var email;

            $.ajax({
            type : ‘POST’,
            beforeSend: function (request){
            request.setRequestHeader(“Authorization”, “YOUR_AUTH_STRING”);
            },
            url : ‘v1/test’,
            data : ’email=’ + email,
            success: function(response){
            console.log(response);
            }
            });

            And make sure you are passing a value in the email variable.

    • sage

      I have the same problem with the api……………..who has a solution to it pls?

  • John

    Hi,

    This is a really great tutorial. But just wanted to point out regarding the “Authorization” code. If you are hosting the php code on some shared hosting you might want to change the name of the field to something else.

    Because i have encounted problem when passing the Request Header with the field as “Authorization”. The Apache server will remove it and hence the server code is not able to find it.

    After asking around it seems that it is because the “Authorization” is uses as a special keyword for the Apache authorization system. Any other header key would be valid, but authorization is stripped off and not passed to PHP when running as CGI in suexec mode. You might want to change the authorization key name to something different.

    Hope this will help.

  • Very Nice, Thank’s

  • chaitu

    i am getting 404 error…tried everything…help me

    • chaitu

      working now thanq..

      • mani

        what was the problem?

      • stralo

        Hey Chaitu, how were you able to get it to work. I’m working on a mac with Poster and I’m getting a 404, too. I went through index.php, commenting stuff out and realized it’s caused by the line $app->run.

        Did you have the same problem? And if so, how did you fix it?

        Thanks!

  • enti

    About “Authorization” problem. Not only server removes “Authorization” header, it capitalizes every word. For example it will change “authorization-key” to “Authorization-Key”. Solution is to enter a header with capitalized letters.

  • Md Ali Azhar

    PassHash Class is USELESS.
    Starting in PHP 5.5, a new API for hashing passwords is being introduced:

    1. To generate hash -> password_hash($password,PASSWORD_BCRYPT);
    -> DEFAULT CostParam=10,
    -> PASSWORD_BCRYPT is used to create new password
    hashes using the CRYPT_BLOWFISH algorithm
    -> password_hash generate the salt randomly for you

    2. To verify hash -> password_verify($password, $hash)

  • Claudio

    It’s missing a method in order to delete an user from table ‘users’ including also the automatic deleting of user’s tasks associated with this method.

    • narendra nauth

      You can easily write this method. What he did was jus to show an example oh how to use it

  • Tiago Pereira

    Hi.
    how can i introduce login with facebook on this great app?

  • mani

    Hi Ravi,
    First thing first, thanks for the awesome tutorial, this is best.

    I have a Android project for school, so I decided to use RESTFULL api, I’m running and hosting my server on aws(amazon web server). when I tried to check if everything work and get a right result back as a jason in “Advance Rest Client” I got 404 error

    404 Not Found

    Not Found
    The requested URL /go_club/v1/register was not found on this server.

    can you help me why it cannot find …/register ?

    Thanks

    • Riko

      Same here, i try to test in my android app it does not work. Did you solve that issue?

      • mani

        not yet 🙁

    • narendra nauth

      You cannot access that route directly. You have to send required params using POST to that route. You can do this easy with AJAX

  • Fuck SQL

    To anyone getting 500 internal serve error my solution was to drop the anonymous user in the database!

    • Rafael

      How to fix error 500?

  • Gokalp

    Super! Thanks a lot..

  • pera

    Ok, can someone show how frontend JS ajax call will looks like?

    • narendra nauth

      This is an example for the register:

      var name = $(‘#name’).val();
      var email = $(‘#email’).val();
      var password = $(‘#password’).val();

      $.ajax({
      type : ‘POST’,
      data : ‘name=’+name+’&email=’+email+’&password=’+password,
      url : ‘v1/register’,
      success: function(response){
      //Process based on response
      }
      });

    • narendra nauth

      Example of create task

      $(‘#create-task’).click(function(){
      var task = $(‘#task’).val();
      $.ajax({
      type : ‘POST’,
      beforeSend: function (request){
      request.setRequestHeader(“Authorization”, “YOUR API KEY”);
      },
      url : ‘v1/tasks’,
      data : ‘task=’ + task,
      success: function(response){
      console.log(response);
      }
      });
      });

  • so much thanks!

  • Pablo

    I can’t get this example to work, I keep getting error: 500 Internal Server Error. Any ideas? thanks!

    • Pablo

      Sorry, I hadn’t enabled mod_rewrite, problem solved.

      • reza

        hi please tell me how do you enable mod_rewrite?
        thank

        • Pablo

          Apache – Apache Modules – select Rewrite module

          • Darren Jones

            Just for info, this is in Wamp in the System tray, left click to get Apache, then Apache Modules

  • william

    Hi Ravi, Thanks for the tutorial. I managed to set up a REST api following your tutorial and its working fine. Am able to fetch data but i have failed to Post data with parameters to it using volley library (android) i get an error BasicNetwork.performRequest: Unexpected response code 400 for http://192.168.1.105/my_api/v1/register. I have tried most of the solutions online but non seems to work. below is the code am using

    final ProgressDialog pDialog = new ProgressDialog(this);

    pDialog.setMessage(“Loading…”);
    pDialog.show();

    // uploade here
    JsonObjectRequest jsonReq = new JsonObjectRequest(Method.POST,
    URL_REGISTRATION, null,
    new Response.Listener() {

    @Override
    public void onResponse(JSONObject response) {
    Log.d(TAG, response.toString());
    pDialog.hide();
    }
    }, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {

    VolleyLog.d(TAG, “Error: ” + error.getMessage());
    pDialog.hide();
    }
    }) {

    /*
    * parsing parameters
    *
    * @see com.android.volley.Request#getParams()
    */
    @Override
    protected java.util.Map getParams() {

    java.util.Map params = new HashMap();
    params.put(“name”, name);
    params.put(“email”, email);
    params.put(“gcm”, regId);

    return params;
    }

    /**
    * Passing some request headers
    * */
    @Override
    public java.util.Map getHeaders()
    throws AuthFailureError {
    HashMap headers = new HashMap();
    headers.put(“Content-Type”, “application/x-www-form-urlencoded”);
    return headers;
    }
    };

    // adding request to volley request queue
    AppController.getInstance().addToRequestQueue(jsonReq);

  • Hi Ravi, this is such a nice tutorial. It has worked fine using chrome advanced Rest Client. But i would like to use this via URL..for instance; /v1/register with POST method to add new user to db via My browser URL.How can i go about?Just need a sample URL format demonstrating how to register new user.

    • You can’t do POST via browser url. You can create a simple HTML form and use the post url to demonstrate.

    • narendra nauth

      As what he said you can simply use a form with name, email and password and set action=”v1/register” for your form and it works fine.

  • mirageservo

    Hi Ravi, you made a very nice complete yet detailed REST example here, its a very interesting stuff, I have tried this and worked with little to no problem on my dev/local deployment. I have a little problem though, first I want to verify if I try to access the route in the browser, (login route) what should I see, a 404 or a valid json response? Currently, I always get a 404 albeit if I throw in a POST request using a REST client I am able to receive a response, am I doing something wrong?

    • narendra nauth

      You cannot access that route directly and have a response. You must send he required params to that route using a form via AJAX or any other method you decide to.

      • mirageservo

        Hi narendra, if I understand you’re message correctly, does that mean, even using Firefox “POSTER” nor Chrome’s “Advance Rest Client” will not work?, likewise, you’re telling me that I’m sending not a valid param format?, anyway, I’ll try your AJAX advise and see if it fixes my error/s.

        • paijopkl

          maybe mirageservo do not place $app->run(); in the end of index.php,

      • mirageservo

        Hi narendra, I have tried sending the params via ajax, and unfortunately, it does not fix my problem, it only yields “an empty string” response, this is starting to crack my head…. could it be the host? but I’ve checked it, and mod_rewrite is enabled, are there anything else I need to consider/reconsider?

  • mirageservo

    I also noticed, if I change the request from post to get such as $app->post(‘/login’, function(){}) into $app->get(‘/login’, function(){}, I’m able to view the response on the browser, I’m getting an understanding of the whole picture though having some little greyed area, Is that because I didn’t send the post key pairs (as there is no way to do that in the browser)?

    • narendra nauth

      Send them using AJAX

  • sureshh testing

    i have tried but responses “Response does not contain any data.”

  • Führer Saleh Mashal

    hi can any one helping me when i do registration for new user it is give me 404 not found how can i fix it. please help me

  • Maw

    Hi, firstly, thank you for nice tutorial how to restful api with slim framework. But I want to do it other way and don’t know how to start. I don’t want users to register but still I don’t want API totally open (so I don’t want that anybody should go to url and get response from server for example from browsers or any external apps). How I can implement something like self-registration of device based on something unique (like device ID?). I don’t want to store any user identity details but still I want to control access to my api to be sure that only from my android app (or basically only from my mobile, web, any apps) will be api accessible? Thank you in advance for any suggestions.

    • narendra nauth

      This can be done with the use of setting the authenticate function on all your methods. You can manipulate it to work with an API username and API key. This will allow you to control access as you desire.

  • kiran

    I think you should add one line after every json response heading.

    ” add following code before app->run() ”

    btw very nice tutorial..
    Thank you !!

  • kiran

    Modify following 2 files
    ——————————–
    1. DbHandler.php

    require_once dirname(__FILE__) . ‘./DbConnect.php’;

    to

    require_once dirname(__FILE__) . ‘/DbConnect.php’;

    2. DbConnect.php

    include_once dirname(__FILE__) . ‘./Config.php’;

    to

    include_once dirname(__FILE__) . ‘/Config.php’;

    just remove .(dot)

    • Jason

      Yes! This fixed my issue. Thank you!

  • Giuseppe Scordo

    Congratulations for your article. has been very useful for my project. When you publish the third part? Thank You

  • Stephen

    Hi,

    Two points.

    1) using uniqid() to create random #s is explicitly discouraged by the php docs: http://php.net/manual/en/function.uniqid.php

    2) using password_hash() (http://php.net/manual/en/function.password-hash.php) is encouraged for hasing passwords.

  • Mustafa

    Please help. When I test “http://localhost/task_manager/v1/tasks”, I am getting “Api key is misssing”.

    • narendra nauth

      That is displayed because in the authenticate method, there is no $headers[‘Authorization’]. You need to pass that within the ajax like the example below:

      $.ajax({
      type : ‘GET’,
      beforeSend: function (request){
      request.setRequestHeader(“Authorization”, “YOUR_API_KEY”);
      },
      url : ‘v1/tasks’,
      success: function(response){
      //Process your success
      }
      error: function(jqXHR, textStatus, errorThrown){
      //Process your error
      }
      });

  • Saurabh Singla

    can you explain is it possible to send HTTP Post request to slim restful api from android? if yes than how ?

  • Dimitrije Randjelovic

    Just wanted to ask if anyone knows or wants to know…
    How would you get all the tasks for one user ? I thought this could be made as a simple social app.
    For example, if you change each work ‘task(s)’ into ‘status(‘s)’ it would be nice to have ‘profile’ where you use getallUSERstatus and a home feed with getALLstatus if you understand me, i am not quite good at MySQL but if there was an example i could learn a bit more and more. Thanks in advance

  • jb patel

    Thanks for this ….How Can i will use this REST api in android replay fast

  • Alois Napitalai

    Cant get this to work in ubuntu,nothing is displayed when I try to access http://localhost/task_manager/v1/tasks

  • marrocmau

    Hi, great work! I tried it in my local LAMP system it’s works fine (I tried all kind of services) BUT when try to use it in personal website (ex: http://www.example.com) i have a problem with Authorization field…it is as though this parameter is not passed to the service (i used “Advanced Rest client” “Postman” and “DHC” -chroms’ apps- for test the Rest service).
    Somebody can help me?

  • Kinza Rian

    thanks !

  • dazziep

    if I miss API key on purpose, I get “Api key is missing” as expected (awesome 🙂 ) BUT I get HTTP status 200, I seem to get 200 for everything, successful or not ???

    • YourBro

      change the methode you use and add in case of faillure echoRespnse(“NEW CODE”, $response) if u see what i mean it will answer with a new http code in case other conditions does not meet, i hope iv helped

    • You can do it in two ways.
      1. Keep the 200 and while parsing the json check for error flag.

      2. Modify the code and use ‘401 – access denied’ when api key is missing.

  • great tutorial

  • Mário Ramos

    Hi Ravi!
    First, thank you for such a nice tutorial.
    Second, not really related to the intent of this project, but if I had the need to connect to different databases (MySQL, SQL Server, etc, via PDO) depending on the route called, what modifications to DBConnect and/or DBHandler and the methods would I need to perform? I’m still a noob in OOP. Thanks!

  • Alex

    I’ve followed all the steps and copied all the files but when I come to test it with the chrome app I do alway get a 404 not found… can’t find out whats wrong :S… some help would be apreciated.

    Thanks

  • Nguyễn Huy

    Hi Ravi!
    Thank you for such a nice tutorial.
    I have anh error when i GET tasks “Fatal error: Call to undefined function apache_request_headers()”

    Can you help me?
    Thanks you!

  • Gaston

    Is there any tutorial on how to use the REST API from my Android applicantion?