Building a REST API with Zend Framework 2
January 17, 2014
What we’re gonna talk about… Overview of REST
• What is the purpose of an API
• Different API Techniques
• The REST Methodology
• Components of REST
• Good PracIces
• Not so Good PracIces
• AuthenIcaIon and OAuth
Building Your API
• SeLng up Your Routes
• SeLng up Your Controllers
• Handling Headers
• Handling JSON
What is an API? An ApplicaIon Program Interface (API) is a set of rouInes,
protocols, and tools for building soRware applicaIons. A
good API makes it easier to develop a program by providing
all the building blocks, which a developer can then use to
put the blocks together.1 Web APIs allow the transmission
of data and acIons across different web applicaIons and
sites securely and through a language
agnosIc interface.
1 definiIon by
Code and Personal Data are Secure
Different Types of APIs There are several different methods for building an API,
however the three leading methods are using
RepresentaIonal State Transfer (REST), Remote Procedure
Calls (RPC), and Simple Object Access Protocol (SOAP).
An Overview of RPC Remote Procedure Calls work in some instances, but as an
API causes problems through Ight coupling that:
• Requires clients to know procedure names
• Has specific procedure parameters and order
• Requires a URI per method/ funcIon (ie create, edit,
delete)
An Overview of SOAP While once widely popular, SOAP has quickly lost ground to
REST based APIs. SOAP based APIs:
• Exposes operaIons/ method calls
• Larger packets of data, XML based
• All calls sent through POST • Can be stateless or stateful
• WSDL – Web Service DefiniIons
SOAP Sample Request
<?xml version="1.0"?> <soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope" soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding"> <soap:Body xmlns:m="http://www.example.org/stock"> <m:GetStockPrice> <m:StockName>IBM</m:StockName> </m:GetStockPrice> </soap:Body> </soap:Envelope>
h`p://www.w3schools.com/soap/soap_example.asp
SOAP Sample Response
<?xml version="1.0"?> <soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope" soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding"> <soap:Body xmlns:m="http://www.example.org/stock"> <m:GetStockPriceResponse> <m:Price>34.5</m:Price> </m:GetStockPriceResponse> </soap:Body> </soap:Envelope>
h`p://www.w3schools.com/soap/soap_example.asp
An Overview of REST REST on the other hand has gained in popularity due to its
simplicity, reduced transfer requirements, and usage of JSON.
REST:
• Returns data, doesn’t expose methods
• Supports XML and JSON
• Uses explicit HTTP Verbs
• Point to point connecIons
• Ajax (JavaScript) Friendly
• Stateless
REST JSON Sample Request
{"user":{"firstName":"John","lastName":"Smith","email":"[email protected]"}!
REST JSON Sample Response {"user":{"firstName":"John","lastName":"Smith","email":"[email protected]"},"_links":{"edit":["href","http:\/\/www.mysite.com\/api\/user\/10"],"message":["href","http:\/\/www.mysite.com\/api\/user\/10\/message"]}}!
REST vs. SOAP REST
• Returns data
• Supports XML and JSON
• Uses CRUD/ HTTP Verbs
• Point to point connecIons
• Ajax (JavaScript) Friendly
• Stateless
SOAP
• Exposes operaIons/ method calls
• All calls sent through POST
• Larger packets of data, XML based
• Can be stateless or stateful
• WSDL Support
Advantage goes to…
Arguments against REST • REST APIs are difficult to build and maintain
• REST is not secure (anyone can access it)
• There are no strict standards for REST APIs
• REST services are not reliable
Arguments against REST • REST APIs are difficult to build and maintain
Um, we’re going to build one in thirty minutes… And as
REST has gained in popularity there are more tools to help
keep documentaIon up to date. As long as basic guidelines
are followed REST APIs can be maintained and
provide backwards compaIbility.
Read how we do it @ h`p://bit.ly/1cCEYjC
Arguments against REST • REST is not secure (anyone can access it)
There are many precauIons we can take to ensure our
REST API only serves those who we want to have access
to the data, including OAuth tokens and IP restricIons.
However, it’s important to remember that like
anything, JavaScript/ AJAX should only be used to
make calls to public or insensiIve data.
Arguments against REST • There are no strict standards for REST APIs
This is true as there is no governing body for REST.
However, we will be going over some general
guidelines that should be used when building your
API and as long as these are followed your
API should be totally awesome.
Arguments against REST • REST services are not reliable
REST services are just as reliable as SOAP APIs when the
proper checks are implemented on the client’s side.
These should include checking the request headers
returned by CURL requests. For this reason it is
important to make sure you are using the correct
response codes and giving easy to understand
error messages.
A Good REST API Offers • Generality – language agnosIc • Familiarity – developers are used to it
• Scalability – can grow with usage/ demand
• SegmentaIon – secIons can be updated independently
• Speed – low data transfer, cacheable • Security – personal data remains protected
• EncapsulaIon – ability to send data as objects
REST APIs provide a concrete barrier
A Good REST API Uses Explicit HTTP Verbs
Create – POST Read – GET
Update – PUT
Delete -‐ DELETE
A Good REST API
Is built for longevity and designed
to evolve…
so#ware design on the scale of decades: every detail is intended to promote so#ware longevity and independent evolu8on. Many of the constraints are directly opposed to short-‐term efficiency. Unfortunately, people are fairly good at short-‐term design, and usually awful at long-‐term design
“
”
-‐ Dr. Roy Fielding
A Good REST API Uses Hypermedia
As
The
Engine
Of
ApplicaIon
State
A Good REST API Uses Hypermedia
As
The
Engine
Of
ApplicaIon
State
HATEOAS relies on the concept that applicaIons will change, data required will change, and paths can change. However, these changes should not effect exisIng clients. Hypermedia is stressed over a server oriented architecture.
A Good REST API Uses Hypermedia
As
The
Engine
Of
ApplicaIon
State
In order to accommodate these changes HATEOAS not only sends back the data requested, but the next possible acIons as urls, this allows the client’s applicaIon to be dynamic while accommodaIng any updates to the API itself, prevenIng V1, V2, V3 endpoints
A Good REST API Uses
Hypermedia
ApplicaIon
Language
Hypermedia ApplicaIon Language provides a format for describing addiIonal resources (ie links) using a _links -> item -> connection type => url grouping.
Content type: applicaXon/hal+json
Using HATEOAS
{"data":{"user":{"fname":"first","lname":"last"}}, "_links":{ "edit":{"href":"/api/user/id/1"}, "message":{"href":"/api/message/id/1"} },"id":"1"}
(Sample JSON Response)
A Good REST API Uses Hypermedia
As
The
Engine
Of
ApplicaIon
State
By sending back the next possible acIons we are able to modify the endpoints without breaking backwards compaIbility… Let’s say we now required the last name in the message endpoint, just to prevent accidental message from being sent…
We can update the endpoint in the response for our clients, allowing them to use the dynamic endpoint to perform the acIon, prevenIng any backwards compaIbility breaks!
Using HATEOAS
{"data":{"user":{"fname":"first","lname":"last"}}, "_links":{ "edit":{"href" : "/api/user/id/1"}, "message":{"href" : "/api/message/id/1/lname/last"} },"id":"1"}
Use JSON instead of XML • Be`er Language Support
• Lightweight (much less code than XML)
• Object Oriented by nature
• Simple to encode/ decode To encode/ decode JSON in PHP simply use the json_encode() and json_decode() funcIons! Many frameworks also have JSON libraries that take care of all of the error checking for you as well!
{"object1":{"object":{"string":"rock","string2":"star"}},"object2":{"string":"hello","string2": "world"},"string":"json is cool"}
Use AuthenXcaXon Tokens • Users should be provided with a unique API key/ idenIfier that allows you to track, limit, and enable features
• An OAuth token should be used to link API keys to accounts instead of requesIng account usernames and passwords
Thro^le API Key Usage Be sure to place limits on the number of calls/ queries an
API key can make per hour or per day to prevent your
applicaIon from becoming bogged down and suscepIble
to DOS a`acks.
This risk can also be reduced by ensuring your
API is operaIng within the cloud and has the
ability to scale to increased traffic demands.
OAuth Tokens Tokens help prevent misuse of the system and limit
access to the control panel. Usernames and passwords
allow hackers to go in beyond the API, oRen accessing
billing and profile informaIon that exposes your clients
to catastrophic damage should this informaIon
fall into the wrong hands
Use HTTP Status Codes Proper use of HTTP status codes give your users immediate feedback on the result. Some of the more widely used Header codes include:
• 200 – OK • 201 – Created • 304 – Not modified • 400 – Bad Request • 401 – Not Authorized • 403 – Forbidden • 404 – Page/ Resource Not Found • 405 – Method Not Allowed • 500 – Internal Server Error
Use HTTP Status Codes You probably shouldn’t use these…
• 418 – I’m a teapot • 420 – Keep your calm
Status Code 420 has gained popularity as it is saIrically returned by Twi`er when there’s been too many requests, however the proper response code for “too many requests” would be 429.
Use DescripXve Error Messages Error messages should give a clear descripIon of what went wrong and how the client can fix their code to avoid the error in the future. Providing a link to addiIonal informaIon and live debugging examples is oRen extremely useful to developers, and allows your error messages to be more compact, but requires you to keep addiIonal documentaIon up to date for your users.
Use DescripXve Error Messages These are not good error messages:
• Something went wrong
• Could not complete acIon
• Invalid parameters
• Hey look, it’s a rocket.
Use DescripXve Error Messages Good Error Messages:
• The AuthenIcaIon token is not recognized, learn more at h`p://…
• A User ID is required to perform this acIon, learn more at h`p://…
• Error Code 313. Read more at h`p://…
Remember! DO NOT BREAK BACKWARDS COMPATIBILITY. Make
sure whatever updates you make to your API do not
cause problems for your exisIng clients.
It is ideal to have unit tests running against your
API to ensure changes do not cause negaIve
or unforeseen consequences.
Versioning (ie: /api/v2/…) For this reason I am not a fan of “versioning” your API.
All releases should be backwards compaIble so that your
users do not need to know which version they are trying
to access! Imagine if you versioned your website!
The excepIon to this is a complete rewrite of
your API, in which case the old API should sIll
be supported unIl clients are migrated.
That’s awesome! Now let’s write some code…
Step 1 – Install ZF2 On the chance that you don’t already have Zend
Framework 2 installed, let’s get it up and running.
We’re going to assume you have PHP (php.net), Git
(git-‐scm.com) and Composer (getcomposer.org)
already installed.
Installing ZF2
mkdir apiproject cd apiproject
1. Create directory on your web server and move into it
git init git remote add https://github.com/zendframework/ZendSkeletonApplication.git source
git pull source master
2. IniIalize Git, add the Skeleton Repository, and Download
3. Run Composer
php composer.phar install
Setup the View JSON Strategy
'view_manager' => array( 'strategies' => array( 'ViewJsonStrategy', ), 'display_not_found_reason' => true, 'display_exceptions' => true, 'doctype' => 'HTML5', /* … */ ),!
Add the ViewJsonStrategy to the strategies array within the view_manager in module\ApplicaIon\config\module.config.php
Setup the Controller Add the UserController to the invokables array within controllers in module\ApplicaIon\config\module.config.php
'controllers' => array( 'invokables' => array( 'Application\Controller\Index' => 'Application\Controller\IndexController', 'Application\Controller\User' => 'Application\Controller\UserController' ), ),!
Remember the alias (leR) you give your controller as we will use that to setup our routes!
Sefng up Routes
// Segmented Route for APIs 'user' => array( 'type' => 'Segment', 'options' => array( 'route' => '/api/user[/:id]', 'defaults' => array( 'controller' => 'Application\Controller\User', ),
), ),!
Add routes to module\ApplicaIon\config\module.config.php where “ApplicaIon” is the module that will be the API library.
Segmented Routes allow you to have dynamic routes. DO NOT include a default AcIon for your REST API endpoints
The AbstractResgulController Zend Framework 2 comes with an abstract controller for
creaIng a REST API built in.
The AbstractRestulController can be found in the vendor/zendframework/zendframework/library/Zend/MVC/Controller/
directory.
Note – you should not modify this file
The AbstractResgulController The abstract controller comes with the following methods,
all set to return header code 405 – method not allowed:
• create($data)
• delete($id)
• deleteList()
• get($id)
• getList()
• update($id, $data)
• replaceList($data)
• patch($id, $data)
• patchList($data)
• opIons()
Sefng up the User Controller Extending the AbstractRestulController we can setup
\module\Application\src\Application\Controller\UserController.php
For this class we will:
• Set the opIons/ header permissions
• Create an event listener
• Start building our API methods
Sefng up the UserController <?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractRestfulController; use Zend\View\Model\JsonModel; class UserController extends AbstractRestfulController { } !
Setup OpXons class UserController extends AbstractRestfulController { protected $collectionOptions = array('GET', 'POST'); protected $resourceOptions = array('GET', 'PUT', 'DELETE'); protected function _getOptions() { if ($this->params->fromRoute('id', false)) { // we have an ID, return specific item return $this->resourceOptions; } // no ID, return collection return $this->collectionOptions; } } !
We’ll use the _getOpIons() method in the opIons() and checkOpIons() methods
Returning Available OpXons public function options() {
$response = $this->getResponse();
// If in Options Array, Allow $response->getHeaders()
->addHeaderLine('Allow', implode(',', $this->_getOptions()));
// Return Response return $response;
}!
Adding an Event Listener
public function setEventManager(EventManagerInterface $events) { // events property defined in AbstractController $this->events = $events; // Register the listener and callback method with a priority of 10 $events->attach('dispatch', array($this, 'checkOptions'), 10); }!
The event listener will check the header when dispatched to see if the behavior/ h`p verb being a`empted is allowed for the resource/ collecIon. If it isn’t, the checkOpIons() method will kill the process and return a status code of 405 to inform the user the method a`empted is not allowed.
Add Event Listener Method public function checkOptions($e) {
if (in_array($e->getRequest()->getMethod(), $this->_getOptions())) { // Method Allowed, Nothing to Do return;
}
// Method Not Allowed $response = $this->getResponse(); $response->setStatusCode(405);
return $response; }!
Add AcXon Methods public function create($data) { // get created service to handle user creation // in this case userAPIService extends UserService and // adds in the _links or available actions to the result $userAPIService = $this->getServiceLocator()->get('userAPIService'); $result = $userAPIService->create($data); $response = $this->getResponse(); $response->setStatusCode(201); // Send Data to the View return new JsonModel($result); }!
Curl –X POST Request Response
Add AcXon Methods public function update($id, $data) { // get created service to handle user updates // in this case userAPIService extends UserService and // adds in the _links or available actions to the result $userAPIService = $this->getServiceLocator()->get('userAPIService'); $result = $userAPIService->update($id, $data); $response = $this->getResponse(); $response->setStatusCode(200); // Send Data to the View return new JsonModel($result); }!
Add AcXon Methods public function deleteList() { $response = $this->getResponse(); $response->setStatusCode(400); $result = array( 'Error' => array( 'HTTP Status' => '400', 'Code' => '123', 'Message' => 'A user ID is required to delete a user', 'More Info' => 'http://www.mysite.com/api/docs/user/delete', ), ); return new JsonModel($result); }!
In most cases you’re going to want to prevent clients from uIlizing the DELETE HTTP verb without providing an ID. In the event they try to access it generically, they will go to the deleteList() method.
Curl –X DELETE Request Response
You now have a funcXonal API! You can conInue to use the following methods (and some
others) to build your API Controllers!
• create($data)
• delete($id)
• deleteList()
• get($id)
• getList()
• update($id, $data)
• replaceList($data)
• patch($id, $data)
• patchList($data)
• opIons()
Remember… You will want to add more features including OAuth 2 and
thro`ling. You can easily do this by creaIng your own
abstract class that extends the AbstractRestulController,
or by taking advantage of a Zend Framework 2 REST
Skeleton App (available on GitHub)
More Resources You can learn more about REST APIs and Zend Framework
2 by visiIng the following:
• REST API Tutorial -‐ h`p://www.restapitutorial.com/
• Apigee REST Design (PDF) -‐ h`p://bit.ly/13vZXAL
• ZF2 GeLng Started -‐ h`p://bit.ly/17Wgmun
• MWOP ZF2 REST -‐ h`p://bit.ly/19rmk9M
• Hounddog ZF2 REST -‐ h`p://bit.ly/12fVW0x
A Final Thought… A good API is harder for your clients to
implement…
…but easier for them (and you) to
maintain. SDKs save lives.
THANK YOU.
@mikegstowe
visit mikestowe.com/slides for more on PHP and Web Development
@ctct_api
A big thank you to Constant Contact for making this presentation possible
Top Related