NOTE: This is the PHP version of this article and its companion app. A Java version is available here.

I have been looking for a lightweight framework to build a RESTful API in PHP. There are a number of good options out there: Slim, Epiphany, Tonic, Recess, and Frapi to name a few. They all seem like good frameworks. In the end, I chose Slim for this project for two main reasons:

It’s very lightweight and focused on REST and nothing else. It supports all the HTTP methods (GET, POST, PUT, DELETE), which was a key requirement for my application.

This article (and its companion app) provides an example of building a complete RESTful API using the different HTTP methods:

GET to retrieve and search data

to retrieve and search data POST to add data

to add data PUT to update data

to update data DELETE to delete data

The application used as an example for this article is a Wine Cellar app. You can search for wines, add a wine to your cellar, update and delete wines.



You can run the application here. The create/update/delete features are disabled in this online version. Use the link at the bottom of this post to download a fully enabled version.



The REST API consists of the following methods:

Method URL Action GET /api/wines Retrieve all wines GET /api/wines/search/Chateau Search for wines with ‘Chateau’ in their name GET /api/wines/10 Retrieve wine with id == 10 POST /api/wines Add a new wine PUT /api/wines/10 Update wine with id == 10 DELETE /api/wines/10 Delete wine with id == 10

Implementing the API with Slim

Slim makes it easy to implement this API in PHP:

<?php require 'Slim/Slim.php'; $app = new Slim(); $app->get('/wines', 'getWines'); $app->get('/wines/:id', 'getWine'); $app->get('/wines/search/:query', 'findByName'); $app->post('/wines', 'addWine'); $app->put('/wines/:id', 'updateWine'); $app->delete('/wines/:id', 'deleteWine'); $app->run(); function getWines() { $sql = "select * FROM wine ORDER BY name"; try { $db = getConnection(); $stmt = $db->query($sql); $wines = $stmt->fetchAll(PDO::FETCH_OBJ); $db = null; echo '{"wine": ' . json_encode($wines) . '}'; } catch(PDOException $e) { echo '{"error":{"text":'. $e->getMessage() .'}}'; } } function getWine($id) { $sql = "SELECT * FROM wine WHERE id=:id"; try { $db = getConnection(); $stmt = $db->prepare($sql); $stmt->bindParam("id", $id); $stmt->execute(); $wine = $stmt->fetchObject(); $db = null; echo json_encode($wine); } catch(PDOException $e) { echo '{"error":{"text":'. $e->getMessage() .'}}'; } } function addWine() { $request = Slim::getInstance()->request(); $wine = json_decode($request->getBody()); $sql = "INSERT INTO wine (name, grapes, country, region, year, description) VALUES (:name, :grapes, :country, :region, :year, :description)"; try { $db = getConnection(); $stmt = $db->prepare($sql); $stmt->bindParam("name", $wine->name); $stmt->bindParam("grapes", $wine->grapes); $stmt->bindParam("country", $wine->country); $stmt->bindParam("region", $wine->region); $stmt->bindParam("year", $wine->year); $stmt->bindParam("description", $wine->description); $stmt->execute(); $wine->id = $db->lastInsertId(); $db = null; echo json_encode($wine); } catch(PDOException $e) { echo '{"error":{"text":'. $e->getMessage() .'}}'; } } function updateWine($id) { $request = Slim::getInstance()->request(); $body = $request->getBody(); $wine = json_decode($body); $sql = "UPDATE wine SET name=:name, grapes=:grapes, country=:country, region=:region, year=:year, description=:description WHERE id=:id"; try { $db = getConnection(); $stmt = $db->prepare($sql); $stmt->bindParam("name", $wine->name); $stmt->bindParam("grapes", $wine->grapes); $stmt->bindParam("country", $wine->country); $stmt->bindParam("region", $wine->region); $stmt->bindParam("year", $wine->year); $stmt->bindParam("description", $wine->description); $stmt->bindParam("id", $id); $stmt->execute(); $db = null; echo json_encode($wine); } catch(PDOException $e) { echo '{"error":{"text":'. $e->getMessage() .'}}'; } } function deleteWine($id) { $sql = "DELETE FROM wine WHERE id=:id"; try { $db = getConnection(); $stmt = $db->prepare($sql); $stmt->bindParam("id", $id); $stmt->execute(); $db = null; } catch(PDOException $e) { echo '{"error":{"text":'. $e->getMessage() .'}}'; } } function findByName($query) { $sql = "SELECT * FROM wine WHERE UPPER(name) LIKE :query ORDER BY name"; try { $db = getConnection(); $stmt = $db->prepare($sql); $query = "%".$query."%"; $stmt->bindParam("query", $query); $stmt->execute(); $wines = $stmt->fetchAll(PDO::FETCH_OBJ); $db = null; echo '{"wine": ' . json_encode($wines) . '}'; } catch(PDOException $e) { echo '{"error":{"text":'. $e->getMessage() .'}}'; } } function getConnection() { $dbhost="127.0.0.1"; $dbuser="root"; $dbpass=""; $dbname="cellar"; $dbh = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $dbh; } ?>

Code Highlights

Lines 7 to 12: Slim helps you route resource URIs to callback functions in response to specific HTTP request methods (e.g. GET, POST, PUT, DELETE). Lines 45 to 46 and 67 to 68: the request object makes it easy to access the request’s data: In this case the JSON representation of a wine object. The approach you use to actually retrieve the data is totally up to you. In this example, I use some simple PDO code, but you can of course use your own data access solution.

Testing the API using cURL

If you want to test your API before using it in a client application, you can invoke your REST services straight from a browser address bar. For example, you could try:

You will only be able to test your GET services that way, and even then, it doesn’t give you full control to test all the content types your API can return.

A more versatile solution to test RESTful services is to use cURL, a command line utility for transferring data with URL syntax.

For example, using cURL, you can test the Wine Cellar API with the following commands:

Get all wines: curl -i -X GET http://localhost/cellar/api/wines

Get all wines with ‘chateau’ in their name: curl -i -X GET http://localhost/cellar/api/wines/search/chateau

Get wine #5: curl -i -X GET http://localhost/cellar/api/wines/5

Delete wine #5: curl -i -X DELETE http://localhost/cellar/api/wines/5

Add a new wine: curl -i -X POST -H 'Content-Type: application/json' -d '{"name": "New Wine", "year": "2009"}' http://localhost/cellar/api/wines

Modify wine #27: curl -i -X PUT -H 'Content-Type: application/json' -d '{"id": "27", "name": "New Wine", "year": "2010"}' http://localhost/cellar/api/wines/27

The jQuery Client

Accessing your API through cURL is cool, but there is nothing like a real application to put your API to the test. So the source code (available for download at the end of this post) includes a simple jQuery client to manage your wine cellar.

Here is the jQuery code involved in calling the services:

function findAll() { $.ajax({ type: 'GET', url: rootURL, dataType: "json", // data type of response success: renderList }); } function findByName(searchKey) { $.ajax({ type: 'GET', url: rootURL + '/search/' + searchKey, dataType: "json", success: renderList }); } function findById(id) { $.ajax({ type: 'GET', url: rootURL + '/' + id, dataType: "json", success: function(data){ $('#btnDelete').show(); renderDetails(data); } }); } function addWine() { console.log('addWine'); $.ajax({ type: 'POST', contentType: 'application/json', url: rootURL, dataType: "json", data: formToJSON(), success: function(data, textStatus, jqXHR){ alert('Wine created successfully'); $('#btnDelete').show(); $('#wineId').val(data.id); }, error: function(jqXHR, textStatus, errorThrown){ alert('addWine error: ' + textStatus); } }); } function updateWine() { $.ajax({ type: 'PUT', contentType: 'application/json', url: rootURL + '/' + $('#wineId').val(), dataType: "json", data: formToJSON(), success: function(data, textStatus, jqXHR){ alert('Wine updated successfully'); }, error: function(jqXHR, textStatus, errorThrown){ alert('updateWine error: ' + textStatus); } }); } function deleteWine() { console.log('deleteWine'); $.ajax({ type: 'DELETE', url: rootURL + '/' + $('#wineId').val(), success: function(data, textStatus, jqXHR){ alert('Wine deleted successfully'); }, error: function(jqXHR, textStatus, errorThrown){ alert('deleteWine error'); } }); } // Helper function to serialize all the form fields into a JSON string function formToJSON() { return JSON.stringify({ "id": $('#id').val(), "name": $('#name').val(), "grapes": $('#grapes').val(), "country": $('#country').val(), "region": $('#region').val(), "year": $('#year').val(), "description": $('#description').val() }); }

Download the Source Code

The source code for this application is hosted on GitHub here. And here is a quick link to the source code download. It includes both the PHP and jQuery code for the application.

I’m interested in your feedback. Let me know what you think and what your experience has been building RESTful-based applications using PHP and jQuery