Professional Documents
Culture Documents
http://code.tutsplus.com/tutorials/creating-an-api-centric-web-application--net-23417
Planning to start working on a new web application? In this tutorial, we'll discuss how
to create an API-centric web application, and explain why this is essential in today's
multi-platform world.
Introduction
API?
For those who are unfamiliar with the term, API is short for Application Programming
Interface. According to Wikipedia:
An application programming interface (API) is a source code based specification
intended to be used as an interface by software components to communicate with each
other. An API may include specifications for routines, data structures, object classes,
and variables.
API
Visualization
In simpler terms, an API refers to a set of functions built into an application, which can
be used by other applications (or by itself, as we'll see later), to interact with the
application. An API is a great way to expose an application's functionality to external
applications safely and securely, since all functionality that these external applications
can do is limited with what functionality is exposed in the API.
This would inevitably lead to more usage of our application, since it can be used
anywhere a person wants.
One of the main advantages of creating an API-centric application is that it helps you
build functionality that can be used by ANY device, be it a browser, a mobile phone, a
tablet, or even a desktop app. All you need to do is to create the API in such a way that
all these devices can communicate with it, and voila! You'll have built a centralized
application that can take input and execute functionality from any device that a person
has!
By creating an application in this manner, we're able to easily take advantage of the
different mediums used by different people. This would inevitably lead to more usage
of an application, since it can be used anywhere a person wants.
To drive the point home, here's an article about Twitter's new redesigned website, which
tells us about how they now use their API to power Twitter.com, essentially making it
API-centric:
One of the most important architectural changes is that Twitter.com is now a client of
our own API. It fetches data from the same endpoints that the mobile site, our apps for
iPhone, iPad, Android, and every third-party application use. This shift allowed us to
allocate more resources to the API team, generating over 40 patches. In the initial page
load and every call from the client, all data is now fetched from a highly optimized
JSON fragment cache.
In this tutorial, we'll be creating a simple TODO list application that is API-Centric and
create one front-end client on the browser that interacts with our TODO list application.
3
By the end, you'll know the integral parts of an API-Centric application, and at the same
time, how to facilitate secure communication between the two. With that in mind, let's
begin!
a Title
a Date Due
a Description
a flag to tell if the TODO Item Is Done
Let's mockup the application as well so we have a guide on how it should look
like afterwards:
SimpleTODO Mockup
$result['success'] = false;
40
$result['errormsg'] = $e->getMessage();
41
}
42
43//echo the result of the API call
44echo json_encode($result);
45exit();
46
47
48
49
50
What we've essentially built here is a simple front controller that does the following:
Besides the index.php file, create three folders: a controllers, models and data folder.
The controllers folder will contain all the controllers we'll be using for the API
server. We'll be building it using the MVC architecture to make the structure of
the API server cleaner and more organized.
The models folder will contain all the data models for the API server.
The data folder will be where the API server saves any data
Go into the controllers folder and create a file called Todo.php. This will be our
controller for any TODO list related tasks. With the functions we'll be needing for our
TODO application in mind, create the necessary methods for the Todo controller:
01
02
03<?php
04class Todo
05{
private $_params;
06
07
public function __construct($params)
08
{
$this->_params = $params;
09
}
10
11
public function createAction()
12
{
13
//create a new todo item
14
}
15
public function readAction()
16
{
17
//read all the todo items
18
}
19
20
public function updateAction()
21
{
//update a todo item
22
}
23
24
public function deleteAction()
25
{
26
//delete a todo item
27
}
28}
29
30
Now, add the necessary functionality to each action. I'll provide the code for the
createAction method and I'll leave it up to you to create the code for the other
methods. If you're not in the mood though, you can just download the source code for
the demo and copy it from there.
01public function createAction()
02{
//create a new todo item
03
$todo = new TodoItem();
04
$todo->title = $this->_params['title'];
05
$todo->description = $this->_params['description'];
06
$todo->due_date = $this->_params['due_date'];
$todo->is_done = 'false';
07
08
//pass the user's username and password to authenticate the user
09
$todo->save($this->_params['username'],
$this10>_params['userpass']);
11
12
//return the todo item in array format
13
return $todo->toArray();
14}
15
Create TodoItem.php inside the models folder so we can create the item creation
code. Take note that I won't be connecting to a database, rather, I'll be saving the
information into files. It should be relatively easy though to make this work with any
database.
01<?php
class TodoItem
02{
03
public $todo_id;
public $title;
04
public $description;
05
public $due_date;
06
public $is_done;
07
08
public function save($username, $userpass)
09
{
//get the username/password hash
10
$userhash = sha1("{$username}_{$userpass}");
11
if( is_dir(DATA_PATH."/{$userhash}") === false ) {
12
mkdir(DATA_PATH."/{$userhash}");
13
}
14
15
//if the $todo_id isn't set yet, it means we need to create
16a new todo item
if( is_null($this->todo_id) || !is_numeric($this->todo_id) )
17
{
18
//the todo id is the current time
19
$this->todo_id = time();
}
20
21
//get the array version of this todo item
22
$todo_item_array = $this->toArray();
23
24
//save the serialized array version into a file
25
$success = file_put_contents(DATA_PATH."/{$userhash}/{$this26>todo_id}.txt", serialize($todo_item_array));
27
28
//if saving was not successful, throw an exception
if( $success === false ) {
29
throw new Exception('Failed to save todo item');
30
}
31
32
//return the array version
33
return $todo_item_array;
}
34
35
public function toArray()
36
{
37
//return an array version of the todo item
38
return array(
39
'todo_id' => $this->todo_id,
'title' => $this->title,
40
'description' => $this->description,
41
'due_date' => $this->due_date,
42
'is_done' => $this->is_done
43
);
8
44
45}
46
47
48
49
50
save() - this saves the TodoItem into a file, as well as set the todo_id for the
TodoItem if necessary
toArray() - this returns an array version of the TodoItem, where the variables
are the array's indexes
Since the API is called via HTTP requests, let's test that API call by calling it through
the browser:
http://localhost/simpletodo_api/?controller=todo&action=create&title=test%20title&des
cription=test%20description&due_date=12/08/2011&username=nikko&userpass=test12
34
If everything worked, you should see a new folder inside the data folder, and inside
that folder, you should see a file with the following content:
createAction() result
Congratulations! You've successfully created an API server and made an API call!
APP ID
and
APP
SECRET
Currently, the API server is set to accept ALL API requests. We'll need to limit it to our
own applications only, to ensure that only our own front-end clients are able to make
API requests. Alternatively, you can actually create a system wherein users can create
9
their own applications that have access to your API server, similar to how Facebook and
Twitter applications work.
Begin by creating a set of id-key pairs for the clients that will be using the API server.
Since this is just a demo, we can use any random, 32 character string. For the APP ID,
let's say it's application APP001.
Open the index.php file again, and then update it with the following code:
01<?php
02// Define path to data folder
03define('DATA_PATH', realpath(dirname(__FILE__).'/data'));
04
//Define our id-key pairs
05$applications = array(
06
'APP001'
=>
'28e336ac6c9423d946ba02d19c6a2632',
//randomly
07generated app key
08);
09//include our models
include_once 'models/TodoItem.php';
10
11//wrap the whole thing in a try-catch block to catch any wayward
12exceptions!
13try {
//*UPDATED*
14
//get the encrypted request
15
$enc_request = $_REQUEST['enc_request'];
16
17
//get the provided app id
18
$app_id = $_REQUEST['app_id'];
19
//check first if the app id exists in the list of applications
20
if( !isset($applications[$app_id]) ) {
21
throw new Exception('Application does not exist!');
22
}
23
24
//decrypt the request
25
$params =
json_decode(trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256,
base64_decode($enc_request),
26$applications[$app_id],
27MCRYPT_MODE_ECB)));
28
//check if the request is valid by checking if it's an array and
29looking for the controller and action
30
if( $params == false || isset($params->controller) == false ||
31isset($params->action) == false ) {
throw new Exception('Request is not valid');
32
}
33
34
//cast it into an array
35
$params = (array) $params;
36
...
...
37
...
38
10
What we've done here is actually implement a very simple way of authenticating our
front-end clients using a system similar to public-private key authentication. Basically,
here is the step-by-step breakdown of how the authentication happens:
Public-key encryption
Now that the API server is secured with an APP ID and APP SECRET, we can begin
programming a front-end client to use the API server.
Advertisement
11
01
02
03<!DOCTYPE html>
04<html>
05<head>
<title>SimpleTODO</title>
06
07
<link rel="stylesheet" href="css/reset.css" type="text/css" />
08
<link
rel="stylesheet"
href="css/bootstrap.min.css"
09type="text/css" />
10
<script src="js/jquery.min.js"></script>
11
<script src="js/jquery-ui-1.8.16.custom.min.js"></script>
12
13
<style>
14
body {
15
padding-top: 40px;
}
16
#main {
17
margin-top: 80px;
18
text-align: center;
19
}
20
</style>
</head>
21
22<body>
<div class="topbar">
23
<div class="fill">
24
<div class="container">
<a class="brand" href="index.php">SimpleTODO</a>
25
</div>
26
</div>
27
</div>
28
<div id="main" class="container">
29
<form class="form-stacked" method="POST" action="login.php">
<div class="row">
30
<div class="span5 offset5">
31
<label for="login_username">Username:</label>
32
<input
type="text"
id="login_username"
33name="login_username" placeholder="username" />
34
35
<label for="login_password">Password:</label>
<input
type="password"
id="login_password"
36
37name="login_password" placeholder="password" />
38
</div>
39
</div>
40
<div class="actions">
<button type="submit" name="login_submit" class="btn
41
primary
large">Login
or Register</button>
42
</div>
43
</form>
44
</div>
45</body>
46</html>
47
48
12
Take note that I've included 2 JavaScript files and 2 CSS files here:
reset.css is your standard CSS reset script. I use the meyerweb.com css reset.
bootstrap.min.css is the Twitter Bootstrap
jquery.min.js is the latest jQuery library
jquery-ui-1.8.16.custom.min.js is the latest jQuery UI library
Next, let's create the login.php file so we store the username and password inside a
session on the client.
01
02<?php
//get the form values
03$username = $_POST['login_username'];
04$userpass = $_POST['login_password'];
05
06
07session_start();
08$_SESSION['username'] = $username;
= $userpass;
09$_SESSION['userpass']
header('Location: todo.php');
10exit();
11
Here, we simply start a session for the user, based on the username and password
combination the user will provide. This acts as a simple combination key, which will
allow a user to access stored TODO items for a specific combination of both the
username and password. We then redirect to todo.php, where we start interacting with
the API server. Before we start coding the todo.php file though, let's first create an
ApiCaller class, which will encapsulate all the API calling methods we'll need,
including encrypting the requests.
Create apicaller.php and put the following inside:
<?php
01class ApiCaller
13
02{
//some variables for the object
03
private $_app_id;
04
private $_app_key;
05
private $_api_url;
06
//construct an ApiCaller object, taking an
07
//APP ID, APP KEY and API URL parameter
08
public function __construct($app_id, $app_key, $api_url)
09
{
10
$this->_app_id = $app_id;
11
$this->_app_key = $app_key;
$this->_api_url = $api_url;
12
}
13
14
//send the request to the API server
15
//also encrypts the request, then checks
16
//if the results are valid
17
public function sendRequest($request_params)
{
18
//encrypt the request parameters
19
$enc_request
=
20base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,
$this->_app_key,
21json_encode($request_params), MCRYPT_MODE_ECB));
22
//create the params array, which will
23
//be the POST parameters
24
$params = array();
25
$params['enc_request'] = $enc_request;
26
$params['app_id'] = $this->_app_id;
27
28
//initialize and setup the curl handler
$ch = curl_init();
29
curl_setopt($ch, CURLOPT_URL, $this->_api_url);
30
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
31
curl_setopt($ch, CURLOPT_POST, count($params));
32
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
33
34
//execute the request
$result = curl_exec($ch);
35
36
//json_decode the result
37
$result = @json_decode($result);
38
39
//check if we're able to json_decode the result correctly
40
if( $result == false || isset($result['success']) == false )
41{
throw new Exception('Request was not correct');
42
}
43
44
//if there was an error in the request, throw an exception
45
if( $result['success'] == false ) {
46
throw new Exception($result['errormsg']);
47
}
48
//if everything went great, return the data
49
return $result['data'];
50
}
51}
14
52
53
54
55
56
57
58
We'll be using the ApiCaller class to send requests to our API server. This way, all the
necessary encryption and cURL initialization code will be in one place, and we won't
have to repeat our code.
http://localhost/simpletodo_api/
Now, let's begin with the todo.php page. First off, let's create some code to retrieve the
current list of todo items for the user nikko with the password test1234 (this is the
user/password combination we used earlier to test the API server).
01<?php
02session_start();
03include_once 'apicaller.php';
04
$apicaller
=
new
05'28e336ac6c9423d946ba02d19c6a2632',
06'http://localhost/simpletodo_api/');
07
08$todo_items = $apicaller->sendRequest(array(
'controller' => 'todo',
09
'action' => 'read',
10
'username' => $_SESSION['username'],
11
'userpass' => $_SESSION['userpass']
12));
13
14echo '';
15var_dump($todo_items);
ApiCaller('APP001',
Go to the index.php page, login as nikko/test1234, and you should see a var_dump()
of the TODO item we created earlier.
15
Congratulations, you've successfully made an API call to the API server! In this code,
we've:
started the session so we have access to the username and userpass in the
$_SESSION
instantiated a new ApiCaller class, giving it the APP ID, APP KEY and the URL
of the API server
send a request via the sendRequest() method
Now, let's reformat the data so it looks better. Add the following HTML to the
code. Don't forget to remove the var_dump()!
todo.php
001<!DOCTYPE html>
002<html>
<head>
003
<title>SimpleTODO</title>
004
005
<link rel="stylesheet" href="css/reset.css" type="text/css" />
<link
rel="stylesheet"
href="css/bootstrap.min.css"
006
007type="text/css" />
<link
rel="stylesheet"
href="css/flick/jquery-ui0081.8.16.custom.css"
type="text/css" />
009
010
<script src="js/jquery.min.js"></script>
011
<script src="js/jquery-ui-1.8.16.custom.min.js"></script>
012
<style>
013
body {
014
padding-top: 40px;
015
}
016
#main {
017
margin-top: 80px;
}
018
019
.textalignright {
020
text-align: right;
021
}
022
023
.marginbottom10 {
024
margin-bottom: 10px;
16
}
025
#newtodo_window {
026
text-align: left;
027
display: none;
028
}
</style>
029
030
<script>
031
$(document).ready(function() {
032
$("#todolist").accordion({
033
collapsible: true
034
});
$(".datepicker").datepicker();
035
$('#newtodo_window').dialog({
036
autoOpen: false,
037
height: 'auto',
038
width: 'auto',
039
modal: true
});
040
$('#newtodo').click(function() {
041
$('#newtodo_window').dialog('open');
042
});
043
});
</script>
044
</head>
045
046<body>
<div class="topbar">
047
<div class="fill">
048
<div class="container">
<a class="brand" href="index.php">SimpleTODO</a>
049
</div>
050
</div>
051
</div>
052
<div id="main" class="container">
<div class="textalignright marginbottom10">
053
<span id="newtodo" class="btn info">Create a new TODO
054
item</span>
055
<div id="newtodo_window" title="Create a new TODO item">
056
<form method="POST" action="new_todo.php">
057
<p>Title:<br /><input type="text" class="title"
058name="title" placeholder="TODO title" /></p>
<p>Date
Due:<br
/><input
type="text"
059
class="datepicker" name="due_date" placeholder="MM/DD/YYYY" /></p>
060
<p>Description:<br
/><textarea
061class="description" name="description"></textarea></p>
062
<div class="actions">
<input
type="submit"
value="Create"
063
name="new_submit"
class="btn
primary"
/>
064
</div>
065
</form>
066
</div>
</div>
067
<div id="todolist">
068
<?php foreach($todo_items as $todo): ?>
069
<h3><a href="#"><?php echo $todo->title; ?></a></h3>
070
<div>
071
<form method="POST" action="update_todo.php">
<div class="textalignright">
072
<a
href="delete_todo.php?todo_id=<?php
echo
073
$todo->todo_id; ?>">Delete</a>
074
</div>
17
<div>
075
<p>Date
Due:<br
/><input
type="text"
076
id="datepicker_<?php echo $todo->todo_id; ?>" class="datepicker"
077name="due_date" value="12/09/2011" /></p>
078
<p>Description:<br
/><textarea
class="span8"
079id="description_<?php echo $todo->todo_id; ?>" class="description"
080name="description"><?php echo $todo->description; ?></textarea></p>
</div>
081
<div class="textalignright">
082
<?php if( $todo->is_done == 'false' ): ?>
083
<input type="hidden" value="false" name="is_done"
084/>
<input type="submit" class="btn" value="Mark as
085
Done?" name="markasdone_button" />
086
<?php else: ?>
087
<input type="hidden" value="true" name="is_done"
088/>
<input
type="button"
class="btn
success"
089
value="Done!" name="done_button" />
090
<?php endif; ?>
091
<input type="hidden" value="<?php echo $todo092>todo_id; ?>" name="todo_id" />
<input type="hidden" value="<?php echo $todo093
094>title; ?>" name="title" />
<input
type="submit"
class="btn
primary"
095value="Save Changes" name="update_button" />
096
</div>
097
</form>
</div>
098
<?php endforeach; ?>
099
</div>
100
</div>
101</body>
102</html>
103
104
18
Pretty cool huh? But this currently does nothing, so let's begin adding some
functionality. I'll provide the code for new_todo.php, which will call the todo/create
API call to create a new TODO item. Creating the other pages (update_todo.php and
delete_todo.php) should be very similar to this one, so I'll leave it up to you to create
those. Open up new_todo.php and add the following code:
01
<?php
02session_start();
03include_once 'apicaller.php';
04
=
new
05$apicaller
'28e336ac6c9423d946ba02d19c6a2632',
06
'http://localhost/simpletodo_api/');
07
08$new_item = $apicaller->sendRequest(array(
09
'controller' => 'todo',
10
'action' => 'create',
'title' => $_POST['title'],
11
'due_date' => $_POST['due_date'],
12
'description' => $_POST['description'],
13
'username' => $_SESSION['username'],
14
'userpass' => $_SESSION['userpass']
15));
16
17header('Location: todo.php');
18exit();
?>
19
ApiCaller('APP001',
As you can see, the new_todo.php page uses the ApiCaller again to facilitate the
sending the todo/create request to the API server. This basically does the same thing as
before:
start a session so it has access to the $username and $userpass saved in the
$_SESSION
instantiate a new ApiCaller class, giving it the APP ID, APP KEY and the URL
of the API server
send the request via the sendRequest() method
redirect back to todo.php
19
Conclusion
There are so many advantages to developing an application that's built around an API.
Want to create an Android application version of SimpleTODO? All the functionality
you would need is already in the API server, so all you need to do is just create the
client! Want to refactor or optimize some of the classes? No problem just make sure
the output is the same. Need to add more functionality? You can do it wihtout affecting
any of the client's code!
Though there are some disadvantages like longer development times or more
complexity, the advantages of developing a web application in this manner greatly
outweight the disadvantages. It's up to us to leverage on this kind of development today
so we can reap the benefits later on.
Are you planning to use an API server for your next web application, or have you
already used the same technique for a project in the past? Let me know in the
comments!
20