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
- POST to add data
- PUT 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:
- http://localhost/cellar/api/wines
- http://localhost/cellar/api/wines/search/Chateau
- http://localhost/cellar/api/wines/5
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
Hey man this is a great article very similar to my methods. But I got some great ideas from this! Thanks for writing this.
Christophe,
This is excellent write up. I have been using the Slim framework for some time now but I remember stuggling with it a little bit at the begining – shame that your tutorial wasn’t there back in September/October;).
L
This is a *really* useful tutorial. I’ve only just started looking into using the Slim Framework and this really helps to break it down into a practical application. I didn’t even think it could be that useful for providing a full RESTful API.
Doing a Backbone alternative version is even better. That’s another lightweight framework I’ve been learning and seeing both side-by-side on a practical example really helps to show the differences between the two and provides a lot of food for thought.
Fantastic work! Thanks for the effort you’ve put in in putting together both tutorials. They’ve come at a good time for me.
Were you able to update the content-type headers to application/json for your api responses?
Very useful, succinct example thank you for posting Christophe.
I was using a newly installed Mac OS x Lion client to run this example.
For the benefit of any other users in a similar position I had to:
1- tell Apache to permit PHP file execution by uncommenting this line (remove hash symbol) in /private/etc/apache2/httpd.conf:
LoadModule php5_module libexec/apache2/libphp5.so
2- install MySql via 64bit DMG at http://www.mysql.com/downloads/mysql/ and start service
3- add a .htaccess file to the cellar/api folder as follows
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
4- add cellar.conf to the /private/etc/apache2/other folder as follows:
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
I initially started by trying to get the site working under my //Users/username/Sites directory following the “read me” instructions but eventually gave up and moved the project to the main WebServer/Documents folder after not being able to get rid of an error: ” File does not exist: /Library/WebServer/Documents/Users”, if anyone has any advice on that I’d be interested to hear it.
twangi:
complete noob to slim and i’m running my LAMP stack on windows… any idea where i would add the cellar.conf to my windows apache ? i do not have an other folder but i do have an extra folder. any pointers are appreciated. thanks!
Added this line to my .htaccess:
RewriteBase /~adams/Cellar
i just use wampserver. when open http://localhost/cellar/ it works well. While I click new wine, it alerts error.
How to fix it? It can’t work to add RewriteBase /~adams/Cellar .
I have other questions.
First, when click ‘delete’,it can’t redirect.
Second, when you modify name ,then save,it can’t display in the left at the same time.
I have already read part3 and final, maybe I can rewrite it. But I really want to know the final version of php.
What php server did you run on?
Well you online test application doesn’t seem to run!! shows no wines and when i deploy the source code over my sever results same. Help!
online testpage doesn’t seem to run it displays no content except the html part. and it results the same when i deploy the source code over my webserver
thank you so much. this was amazingly helpful :)
Hi Christophe, I got the app *almost* running. After all settings, the database is imported but the wine data wouldn’t load (right on homepage). Any idea? Thanks for your help!
I figured out: pay attention to rootURL and JSON, firebug is your friend! Thanks for the useful tutorial Christophe!
the add function is not working any idea ?
also the update function give me parser error any solution so far?
@Amany:
Please comment the line
Line No. 46: error_log(‘addWine\n’, 3, ‘/var/tmp/php.log’); in addWine() function.
I had the same problem. Now its working for me
this is great script, just a question, how do i go about getting this to support chinese, i had it all going but the json return ?????? and it displays correctly in phpmyadmin, any help would be great cheers
Jason
Hello,
I’ve been struggling with Backbone and your example for 2 days now (no kidding… I’m getting crazy!) and still haven’t found a way to make it work. I’m sorry and I must agree I’m no real programmer, but I would really like to understand all this :
- I’ve downloaded your app (part1) with the PHP back-end. I’ve installed it on my localhost, re-read the changes you mention in you readme file… and I still get a 404 error on …/…/api/wines (like file index.php doesn’t exists) in my Firebug console! Result : no wine in the list. I feel like the Slim framework doesn’t do its job. Isn’t it supposed to catch the call and redirect it to index.php which then looks in the database?
- I’ve tried putting it on a web server (feeling my localhost might be wrong). I had a 500 internal error on a first server (on a free webserver). I had a 404 error again on my website server.
Sorry for this long comment which is actually more a desperate call for help, but I did spend about 15 hours in 2 days on that and I am really getting crazy. I am actually totally unable to use Backbone with server side data saving (quite a shame…). Eventually I’d like to change your Slim usage by working on files instead of a Mysql database, (so my database is not set on my remote servers tests _although I set it on my localhost), but I don’t think this is the problem.
If you (or anybody reading this) feel like helping me a little with that one, I’d greatly appreciate. I have the feeling I’m missing quite an important thing here…
You can email me if you prefer.
Thanks !
Fred.
Well… After struggling with all those news things to me, I found out it was a mod_rewrite problem on the server…
My bad….
Fred, what was the solution? I’ve gone through my httpd.conf and /private/etc/apache2/users/username.conf file and changed the AllowOverrride from None to All, restarted, and still nothing.
http://rudyegenias.wordpress.com/2006/08/21/enabling-mod-rewrite-in-xampp/
the above link resolved my problem.
http://rudyegenias.wordpress.com/2006/08/21/enabling-mod-rewrite-in-xampp/
I am using xampp, the above link resolved my problem.
i use xampp and the server refuses the http delete method.
Does anyone know how to enable the delet method on xampp.
I’m not using Slim framework yet. I assumed your function contain PDO code to manage database, could you check if your database account has “delete” privilege?
Fantastic example, thanks! There are lots of REST tutorials about, but few worked examples that really illustrate the concepts.
Thanks for this post. You have helped me get started with Slim!!
Awesome tutorial.
I am trying to adjust this to be able to upload an image, in the example you provided maybe like an image of a wine bottle, is this easy to add to the API?
Thanks!!
Titus
nice example but what about header for json and how to set http cache settings
thanks
Very nice article, guy! :)
Thank you for this powerfull REST+Ajax demonstration!
[]‘s
Silvio Clécio.
Hi Christophe,
Why did you choose to use $db=getConnection(); instead of a global $db variable?
Thanks for the insight.
Excellent tutorial! I was able to adapt this to a project I had already started. Thank you so much!
Excellent tutorial. Really taught me a lot about AJAX and JSON. One question. Any idea why it won’t work in IE9? I get just a page of blank fields in that browser. I get this is probably intended as an iOS Web app etc, but I’m just curious as to what the offender is in IE. Works great in Chrome, Safari (for iOS) and Firefox. I tried implementing this script [ http://www.json.org/js.html ] with no results.
Thanks!
I’m trying to implement this on my webserver I keept gettinga 404 in my console when the system first loads trying to call the initial findAll() function. I’ve double checked the rootURL variable. I’ve tried enabling and disabling RewriteBase. No matter what I do I can’t seem to get it to load. You can see it at http://dev.lonewolfdigital.com/winecellar/
Any help would be greatly appreciated.
your API http://dev.lonewolfdigital.com/winecellar/api/wines is not generating json respone. maybe there is problem regarding accessing your database?
Why have comments if you never answer them?
Great article! thanks!!
For the for the SlimPHP API to work on MacOS X 10.7 (Lion) I had to add Options +FollowSymlinks to my .htaccess file (and adjust the RewriteBase path accordingly). My .htaccess file:
Options +FollowSymlinks
RewriteEngine On
# Some hosts may require you to use the `RewriteBase` directive.
# If you need to use the `RewriteBase` directive, it should be the
# absolute physical path to the directory that contains this htaccess file.
#
RewriteBase /api/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
the add fun is not working in the demo and the same when i install it on my machine as it gives
internal server error although the update and delete are working perfrectly
any solution so far?
Excellent script work.Really its very useful.Thank u so much……….
You sir, are a bad ass.
its a great tutorial but when trying to add new wine i am getting error “addWine error:parsererror”. i am using the code from the sample code. but still getting error any body resolved the issue.
How can we also send with Post some parameters in the URL like xxxxx.com/5/?name=eyal
so i want to transfer 5 as a URL parameter, and name=eyal as a query parameter
how can i get those value
Hi,
Eyan I have the same problem…
AddWine doesn’t work. Someone can tell us how he make it work ? only functions with GET work well.
$request = $app->request()
$request->get(‘name’) // to get the value of eyal
for add a new wine for example, how can i add query parameter like ?type=red&size=1.5
?
How can I deploy project in PHP into my server with using the Slim framework ? I tried to solve the problem but I can’t find any clear solution ?
hello, i have problem with post function. it doesn’t work very well. I don’t know why ? can you help me please ?
Excellent tutorial!!!
How does Slim framework handle load? Say 100 transactions/sec or more.
it works fine for me! thanks!
my concern now is the security issue. because you can delete an item using command line.
Great article. Are there any resources on how to best secure the API calls with Slim? I am not using user logins or OAuth, just need to generate secure keys to transfer the data on the backend.
Hi, nice article. So far I’ve used only Tonic but I want to know about other PHP Restful frameworks. With Tonic we can setup our script to accept and provide certain mimetype. For example, a GET method to http://localhost/cellar/api/wines/5 which request ACCEPT array is set to contain image/png would give an image, text/html would give a html, application/pdf would give a pdf file, and so forth. Can Slim do this?
Same problem : How can we also send with Post some parameters in the URL like xxxxx.com/5/?name=eyal
so i want to transfer 5 as a URL parameter, and name=eyal as a query parameter
how can i get those value
Someone found a solution ?
Thanks
Nice post, thank you! :)
I really love this example. Clean, easy and the ajax makes it feel nice. The only problem I have is a massive hesitation when I click on a wine, even on localhost. How is it that with no latency it can take 2-3 seconds for the wine data to show up. I would think that it should load up in under a millisecond. Where is the bottleneck?
So after a few hours of debugging and narrowing it down I discovered that the slowdown came from using “localhost” to connect to the db. By using “127.0.0.1″ the hesitation is cleared up.
I found that the ruby on rails based solution is also very simple and straightforward, the RESTful url looks slightly different.
I create a similar solution by running a few commands. No single line of code.
rails new wine_cellar
rails generate scaffold Wine name:string grapes:string country:string region:stri
ng year:number note:text imgsrc:string
rake db:migrate
rails server
That is it.
The RESTful interface looks like:
curl -i -X GET http://localhost:3000/wines.json
The tutorial is great. I successfully deployed the working files to my localhost server and it works perfectly fine after I comment line 45 on index.php. Hopefully I can integrate Slim framework to my knockout js & jquery mobile project.
I have only one question, how api/index.php called in our app? Since index.php was not declared in js/main.js.
Wonderful tutorial …
hello there! thanks for this nice post!
for those having problem with the .htaccess file (http://localhost/cellar/api/wines 404 (Not Found) ), take a glance at httpd.conf file (you might have to change AllowOverride All)
Thanks for the post. I am very new to php, mysql and REST but I could create my first working app exclusively using this article alone.
Hi,
thx this is great, but i’ve one question (maybe off-topic). How to secure the access? Is there any simply way to implement (simple) authentication?
Any Ideas?
UR
Hi! I’ve loved your article.
I would like to leave two little suggestions.
As other users has asked, you could set the encoding of the responses to json using this before your echo’s:
$app = Slim::getInstance();
$app->contentType(‘application/json’);
And to get the correct response code you could use this in the catch block, just before your echo:
$app = Slim::getInstance();
$app->response()->status(500); //500 == internal server error or 400 == bad request
Nice tutorial! Actually I just made my first (working) API with it. But now I want to hook my API up to a mobile app I am working on (phonegap etc). But it is not clear to me how I can load JSON data from the Slim Api. Do I actually need JSONP for this, and is Slim suited for that? It seems like quite a standard application, but I can’t find how to connect the API to my App.
Thanks in advance for any help, tips & solutions!
hi this is is very useful to me . thanq for giving this information.
I tend not to leave a leave a response, but I browsed some remarks here RESTful
services with jQuery, PHP and the Slim Framework |
Christophe Coenraets. I do have a couple of
questions for you if you do not mind. Is it just me or do a few of these comments look like they are left by brain
dead people? :-P And, if you are posting at additional online sites,
I’d like to keep up with you. Could you post a list of the complete urls of your shared sites like your Facebook page, twitter feed, or linkedin profile?
i think they need to read also the Slim documentation.. i have many question too.. but after i read the documentation.. im very happy using slim now! and also some googling! =)
In using this code, I get Class ‘Slim” not found. Did I not install Slim right?