HATEOAS JSON HAL — Hypermedia Application Language for REST APIs
In this tutorial, you will learn about HATEOAS JSON HAL. We cover key concepts, practical examples, and best practices to help you master this topic.
JSON HAL (Hypertext Application Language) is a standard format for representing REST resources with hypermedia links, using _links for navigation and _embedded for related resources, making APIs self-describing and discoverable.
What You'll Learn
- HAL resource structure with _links and _embedded
- Curies for link relation documentation
- HAL browser for API exploration
- Implementing HAL in Node.js and Java
- Embedding related resources
- Link templating with RFC 6570
Why It Matters
HAL provides a consistent, well-documented format for hypermedia APIs. Clients can navigate the API by following links instead of hardcoding URLs. DodaTech's Durga Antivirus Pro uses HAL for its device management API, allowing the dashboard to discover related resources (threats, scans, settings) from each device resource.
Real-World Use
A client fetches a device resource and receives _links to its threats (/devices/1/threats), scan history (/devices/1/scans), and configuration (/devices/1/config). The client renders navigation options without knowing the URL structure in advance.
flowchart TB
A["GET /devices/1"] --> B["Device Resource (HAL)"]
B --> C["_links.self: /devices/1"]
B --> D["_links.threats: /devices/1/threats"]
B --> E["_links.scans: /devices/1/scans"]
B --> F["_links.config: /devices/1/config"]
B --> G["_embedded: { threats: [...] }"]
C --> H["Client navigates via links"]
D --> H
E --> H
style A fill:#dbeafe,stroke:#2563eb
Code Examples
Example 1: HAL Resource Structure
{
"_links": {
"self": { "href": "/devices/1" },
"threats": { "href": "/devices/1/threats" },
"scans": { "href": "/devices/1/scans" },
"config": { "href": "/devices/1/config" },
"curies": [
{
"name": "dt",
"href": "https://docs.dodatech.com/rels/{rel}",
"templated": true
}
],
"dt:quarantine": { "href": "/devices/1/quarantine" },
"dt:analyze": { "href": "/devices/1/analyze" }
},
"_embedded": {
"threats": [
{
"_links": {
"self": { "href": "/threats/101" }
},
"threatId": "101",
"name": "Ransomware-X"
}
]
},
"deviceId": "1",
"name": "Office-PC",
"os": "Windows 11",
"status": "active"
}
Example 2: HAL Implementation in Node.js
const express = require('express');
const app = express();
// HAL helper
function hal(resource, links, embedded = {}) {
const result = { ...resource, _links: {} };
for (const [rel, href] of Object.entries(links)) {
if (typeof href === 'object') {
result._links[rel] = href;
} else {
result._links[rel] = { href };
}
}
if (Object.keys(embedded).length > 0) {
result._embedded = embedded;
}
return result;
}
// Device endpoint with HAL
app.get('/devices/:id', (req, res) => {
const device = getDevice(req.params.id);
const threats = getThreatsForDevice(req.params.id);
const response = hal(
{ ...device },
{
self: `/devices/${device.id}`,
threats: `/devices/${device.id}/threats`,
scans: `/devices/${device.id}/scans`,
config: `/devices/${device.id}/config`,
'dt:quarantine': `/devices/${device.id}/quarantine`,
curies: [{
name: 'dt',
href: 'https://docs.dodatech.com/rels/{rel}',
templated: true,
}],
},
{
threats: threats.map(t => hal(
{ ...t },
{ self: `/threats/${t.id}` }
)),
}
);
res.json(response);
});
// Collection with HAL
app.get('/devices', (req, res) => {
const devices = getAllDevices();
const response = hal(
{ total: devices.length },
{
self: '/devices',
next: devices.length > 20 ? '/devices?page=2' : null,
},
{
items: devices.map(d => hal(
{ ...d },
{ self: `/devices/${d.id}` }
)),
}
);
res.json(response);
});
Example 3: HAL Client Navigation
class HalClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.cache = new Map();
}
async fetch(url) {
if (this.cache.has(url)) {
return this.cache.get(url);
}
const response = await fetch(url);
const resource = await response.json();
this.cache.set(url, resource);
return resource;
}
async followLink(resource, rel) {
const link = resource._links[rel];
if (!link) {
throw new Error(`Link '${rel}' not found`);
}
return this.fetch(link.href);
}
async navigate() {
// Start at root
const root = await this.fetch('/api');
console.log('Available actions:', Object.keys(root._links));
// Follow to devices
const devices = await this.followLink(root, 'devices');
console.log('Devices:', devices._embedded.items.length);
// Follow to first device's threats
const firstDevice = devices._embedded.items[0];
const threats = await this.followLink(firstDevice, 'threats');
console.log('Threats:', threats);
// Follow to quarantine action
await this.followLink(firstDevice, 'dt:quarantine');
console.log('Device quarantined');
}
}
// Usage
const client = new HalClient('https://api.dodatech.com');
await client.navigate();
Common Mistakes
- Not including a self link — every resource must have a self link that returns the current resource. Clients use this for Caching and identity.
- Hardcoding URLs in clients — the point of HATEOAS is that clients discover URLs via links. Never hardcode URLs when you can follow links.
- Forgetting curies for custom relations — custom link relations without documentation are meaningless. Use curies to link to documentation.
- Embedding too much data — _embedded is for related resources the client likely needs immediately. Don't embed entire collections; use paginated links instead.
- Using _links as an afterthought — links should be a first-class part of the resource design, not added as an afterthought.
Practice Questions
- What is the purpose of the
_linksproperty in HAL? - How do curies help document custom link relations?
- When should you use
_embeddedversus separate links? - How does a HAL client navigate an API without documentation?
- What is the difference between a link and an embedded resource?
Challenge: Design a HAL API for a threat management system where each device resource links to its threats, scans, and configuration. Include curies for documentation, embedded recent threats, and a collection endpoint with pagination links.
Mini Project
Build a HAL-based REST API for a device management system with: HAL-formatted resources with self links, curies for custom relations, embedded related resources, collection endpoints with pagination links, and a JavaScript HAL client that navigates the API programmatically.
FAQ
What's Next
Learn about HATEOAS link formats
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro