Skip to Content
Course content

200: Building HATEOAS-Style APIs

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, why can't I just give the client a Swagger doc and let them build the URLs?

I get this one a lot. Why spend the extra effort embedding links in your JSON when you can just document your API endpoints and call it a day? The answer is decoupling. When you hardcode URLs into a frontend or a mobile app, you're creating a rigid contract. If you decide to change /api/orders/123/cancel to /api/orders/123/void, you've just broken every single client implementation.

With HATEOAS (Hypermedia as the Engine of Application State), the client doesn't need to "know" the URL. It only needs to know the relationship (the rel). The client looks for the link labeled "cancel" and follows whatever URL the server provided. If you change the URL on the backend, the client keeps working because it's just following the map you gave it in the response. It turns your API from a static set of endpoints into a navigable conversation.

How do I actually structure these links so they don't clutter my data?

You don't want your business data and your navigation links mashed together in one flat object. That's a nightmare to parse. The common practice is to wrap your links in a dedicated key, usually _links. This separates the "what this thing is" from "what I can do with this thing."

Let's look at an Order resource. If I'm requesting a specific order, I shouldn't just send the order details; I should send the actions available for that order's current state.

<?php
// A simplified Order Resource representation
$order = [
    'id' => 5421,
    'status' => 'pending',
    'total' => 120.50,
    'currency' => 'USD',
    '_links' => [
        'self' => [
            'href' => '/api/orders/5421',
            'method' => 'GET'
        ],
        'cancel' => [
            'href' => '/api/orders/5421/cancel',
            'method' => 'POST'
        ],
        'payment' => [
            'href' => '/api/orders/5421/pay',
            'method' => 'POST'
        ]
    ]
];

echo json_encode($order, JSON_PRETTY_PRINT);
?>

How do I handle the logic of which links to show and when?

This is where the "Application State" part of HATEOAS really kicks in. You shouldn't show a "cancel" link if the order has already been shipped. The server is the source of truth for business logic, so the server should decide which transitions are valid.

I usually recommend a small helper method or a dedicated Link Generator class. You don't want this logic leaking into your controllers. Here is how I'd handle the conditional logic for an order system:

<?php
class OrderLinkGenerator {
    public function getLinks(Order $order): array {
        $links = [
            'self' => ['href' => "/api/orders/{$order->id}", 'method' => 'GET']
        ];

        // Only allow cancellation if the order is still pending
        if ($order->status === 'pending') {
            $links['cancel'] = [
                'href' => "/api/orders/{$order->id}/cancel",
                'method' => 'POST'
            ];
        }

        // Only allow payment if it's pending and not already paid
        if ($order->status === 'pending' && !$order->isPaid()) {
            $links['pay'] = [
                'href' => "/api/orders/{$order->id}/pay",
                'method' => 'POST'
            ];
        }

        // Allow tracking only after it has been shipped
        if ($order->status === 'shipped') {
            $links['track'] = [
                'href' => "/api/orders/{$order->id}/track",
                'method' => 'GET'
            ];
        }

        return $links;
    }
}
?>

Now, the frontend is incredibly simple: if (response._links.cancel) { showCancelButton(); }. The frontend no longer needs to know the business rules for when an order can be cancelled; it just reacts to the presence of the link.




📋 Practical Task

Exercise: Implementing State-Based Links for a Bank Account API

You are building a Banking API. You need to create a AccountLinkGenerator class that provides HATEOAS links based on the account's balance and status. Implement the following logic:

  • All accounts: Should have a self link.
  • Active accounts: Should have a deposit link.
  • Active accounts with a balance > 0: Should have a withdraw link.
  • Frozen accounts: Should NOT have deposit or withdraw links, but should have a request_unfreeze link.

Create a PHP script that defines an Account class (with properties id, balance, and status) and the AccountLinkGenerator class. Test it by creating one "Active" account with a positive balance and one "Frozen" account, then print the resulting JSON for both.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.