If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
If I tell you to go to a webpage, I'll probably tell you to go to its homepage - like KnpUniversity.com - because I know once you get there, you'll be able see links, follow them and find whatever you need. And if you think about it, there's no reason an API has to be any different. And this is an idea that's catching on.
So right now, if I go to just /api
in the Hal browser we get a 404 response,
because we haven't built anything for this yet. I'm thinking, why not build
a homepage that people can go to and get information about how to use our
API?
I'll build this in ProgrammerController
just for convenience. Let's add the
new /api
route and point it at a new method called homepageAction
:
... lines 1 - 17 | |
class ProgrammerController extends BaseController | |
{ | |
protected function addRoutes(ControllerCollection $controllers) | |
{ | |
// the homepage - put in this controller for simplicity | |
$controllers->get('api', array($this, 'homepageAction')); | |
... lines 24 - 41 | |
} | |
... lines 43 - 185 | |
} |
Inside of here, I'm not going to return anything yet - let's just reuse that
same createApiResponse
, and I'll pass it an empty array:
... lines 1 - 43 | |
public function homepageAction() | |
{ | |
return $this->createApiResponse(array()); | |
} | |
... lines 48 - 187 |
If we try this, we get an empty response, but it has a valid application/hal+json
.
So that's all we really need to do to get an endpoint working.
We know that every URL is a resource in the philosophical sense: /api/programmers
is a collection resource and /api/programmers/Fred
represents a programmer
resource. And really, the /api
endpoint is no different. By the way, I
did not mean to leave the /
off of my path - it doesn't matter if you
have it, but I'll add it for consistency.
... lines 1 - 19 | |
protected function addRoutes(ControllerCollection $controllers) | |
{ | |
// the homepage - put in this controller for simplicity | |
$controllers->get('/api', array($this, 'homepageAction')); | |
... lines 24 - 41 | |
} | |
... lines 43 - 187 |
So far, every time we have a resource, we have a model class for it. Why not
do the same thing for the Homepage resource? Create a new class called Homepage
:
namespace KnpU\CodeBattle\Model; | |
... lines 4 - 5 | |
/** | |
* A model to represent the homepage resource | |
* | |
... lines 10 - 15 | |
*/ | |
class Homepage | |
{ | |
} |
And without doing anything else, I'll create a new object called $homepage
.
Don't forget to add your use
statement whenever you reference a new
class in a file. And instead of the empty array, we'll pass createApiResponse()
the Homepage
object:
... lines 1 - 45 | |
public function homepageAction() | |
{ | |
$homepage = new Homepage(); | |
return $this->createApiResponse($homepage); | |
} | |
... lines 52 - 191 |
So every time we have a resource, we have a class for it. It doesn't really matter if the class is being pulled from the database, being created manually or being populated with data from something like Elastic Search.
If we hit Go on the browser, we get the exact same response back: no difference
yet. But now that we have a model class, we can start adding things to it.
And since every resource has a self
link, let's add that to Homepage
too. I'll grab the Relation
from programmer for convenience. And of course,
grab the use
statement for it: I know I'm repeating myself over and over
again!
Now, we need the name of the route. So let's go give this new route a name:
api_homepage
:
... lines 1 - 20 | |
protected function addRoutes(ControllerCollection $controllers) | |
{ | |
... line 23 | |
$controllers->get('/api', array($this, 'homepageAction')) | |
->bind('api_homepage'); | |
... lines 26 - 43 | |
} | |
... lines 45 - 191 |
We'll use this in the Relation
. And because there aren't
any wildcards in the route, we don't need any parameters
.
... lines 1 - 4 | |
use Hateoas\Configuration\Annotation as Hateoas; | |
/** | |
* A model to represent the homepage resource | |
* | |
* @Hateoas\Relation( | |
* "self", | |
* href = @Hateoas\Route( | |
* "api_homepage" | |
* ) | |
* ) | |
*/ | |
class Homepage | |
{ | |
} |
Cool!
Let's try this out! We have a link! Now, I know this doesn't seem very useful
yet, because we have an API homepage that's linking to itself, but now if
we wanted to, we could link back to the homepage from other resources. So
if we were on the /api/programmers
resource, we could add a link back
to the homepage. When an API client GET's that URL, they'll see that there's
a place to get more information about the entire API.
When you look at the Links section, there are a few other columns like title,
name/index and docs. One of the things that this is highlighting is that
your links can have more than just that href
. So in Homepage
, let's give
the link a title of "The API Homepage":
... lines 1 - 6 | |
/** | |
* A model to represent the homepage resource | |
* | |
* @Hateoas\Relation( | |
* "self", | |
* href = @Hateoas\Route( | |
* "api_homepage" | |
* ), | |
* attributes = {"title": "Your API starting point" } | |
* ) | |
*/ | |
class Homepage | |
... lines 19 - 21 |
Let's go back to the browser to see that title. And now we can give any link a little bit more information.
Let's keep going! Since this is the API homepage, you probably want to give
the client a nice welcome message and maybe even link to the human-readable
documentation. Even though this Homepage
class isn't being pulled from the
database doesn't mean that we can't have properties. Let's create a $message
property and set that to some hard-coded text. You could even put your documentation
URL here:
... lines 1 - 17 | |
class Homepage | |
{ | |
private $message = 'Welcome to the CodeBattles API! Look around at the _links to browse the API. And have a crazy-cool day.'; | |
} |
And now, our API homepage is getting interesting! But the real purpose of
this homepage is to have links to the actual resources that the API client
will want. This is really easy as well. The most obvious resource the client
may want is the programmers collection. So let's do this here. We'll say
programmers
and we'll link to /api/programmers
. That route doesn't have
a name yet, so let's call it api_programmers_list
:
... lines 1 - 20 | |
protected function addRoutes(ControllerCollection $controllers) | |
{ | |
... lines 23 - 31 | |
$controllers->get('/api/programmers', array($this, 'listAction')) | |
->bind('api_programmers_list'); | |
... lines 34 - 44 | |
} | |
... lines 46 - 192 |
And now we can use it in the Relation
. To be super friendly, we'll give
this link a nice title as well:
... lines 1 - 6 | |
/** | |
... lines 8 - 16 | |
* @Hateoas\Relation( | |
* "programmers", | |
* href = @Hateoas\Route( | |
* "api_programmers_list" | |
* ), | |
* attributes = {"title": "The list of all programmers" } | |
* ) | |
*/ | |
class Homepage | |
... lines 26 - 29 |
So let's hit Go. Now we have a really great homepage. We can come here, we can see the message with a link to the documentation, we can visit all of the programmers, and now we're dangerous. From there we can follow links to a programmer, to that programmer's battles, and anything else our links lead us to.
Hey @cameron!
Excellent question :).
The long answer is that some people believe that these formats and links will become so standardized, that you could point an api client at the homepage of an api, and it would organically crawl the endpoints and follow the links until it found what it’s looking for (like a user browsing a web site).
But that’s a bit “science fiction” to me ;). Especially if you’re building an API for yourself.
But they are still really useful - especially the IRI links that api platform uses. Basically, I see links as a useful way to help pass more info to my javascript so that I don’t need to hardcode url patterns there. For example, if I make a GET request to /api/cheeses and then I want to fetch page 2 of the results, I don’t need to even care that we do this by adding ?page=2 (and that’s especially cool to not worry about if I already have other filters on the url like ?title=foo). Nope, I can just read the “next” link from the response and follow it. That’s not an “earth shatteringly cool thing”... but it *is* kinda nice.
The same is true for the IRI strings. If I fetch a cheese listing and I want more info about its owner, I know I can make a GET request to the owner IRI string to do that.
So... links are helpful! Done well, they make our life easier. But they’re not any crazy, magic solution.
Cheers!
Thanks. it would be good to have this context prior to beginning the "how to" component of hateoas.
// composer.json
{
"require": {
"silex/silex": "~1.0", // v1.3.2
"symfony/twig-bridge": "~2.1", // v2.7.3
"symfony/security": "~2.4", // v2.7.3
"doctrine/dbal": "^2.5.4", // v2.5.4
"monolog/monolog": "~1.7.0", // 1.7.0
"symfony/validator": "~2.4", // v2.7.3
"symfony/expression-language": "~2.4", // v2.7.3
"jms/serializer": "~0.16", // 0.16.0
"willdurand/hateoas": "~2.3" // v2.3.0
},
"require-dev": {
"behat/mink": "~1.5", // v1.5.0
"behat/mink-goutte-driver": "~1.0.9", // v1.0.9
"behat/mink-selenium2-driver": "~1.1.1", // v1.1.1
"behat/behat": "~2.5", // v2.5.5
"behat/mink-extension": "~1.2.0", // v1.2.0
"phpunit/phpunit": "~5.7.0", // 5.7.27
"guzzle/guzzle": "~3.7" // v3.9.3
}
}
Hey folks, what is the purpose of hateoas and it's links? Can it be used to auto generate a graphQL schema? Do it have other uses? Because a Dev can simply look at the documentation to understand what endpoints to access.
And probably just as importantly: what situations is it unnessacary to add hateoas? For instance, if I'm the only consumer of the API and I'm already familiar with it, would it be nessacary to spend time to add hateoas?