[](https://www.npmjs.com/package/@hateoas-ts/resource) [](https://www.npmjs.com/packag
npm install @hateoas-ts/resource




> Type-safe HATEOAS client for HAL APIs with automatic link navigation, caching, and middleware support.
``bash`
npm install @hateoas-ts/resourceor
yarn add @hateoas-ts/resourceor
pnpm add @hateoas-ts/resource
`typescript
import { createClient, Entity, Collection } from '@hateoas-ts/resource';
// 1. Define your entity types
type Post = Entity<{ id: string; title: string; content: string }, { self: Post; author: User }>;
type User = Entity<{ id: string; name: string; email: string }, { self: User; posts: Collection
// 2. Create client
const client = createClient({ baseURL: 'https://api.example.com' });
// 3. Navigate resources
const user = await client.go
console.log(user.data.name);
// 4. Follow HATEOAS links - no URL hardcoding!
const posts = await user.follow('posts').get();
for (const post of posts.collection) {
console.log(post.data.title);
}
`
| Concept | Description |
| -------------- | ----------------------------------------------------------- |
| Entity | Type-safe resource definition with data, links, and actions |
| Collection | Paginated list of entities with navigation links |
| Resource | Represents an API endpoint with HTTP methods |
| State | Contains resource data, links, and collection items |
| Middleware | Intercept and modify requests/responses |
`typescript
// GET request (cached automatically)
const user = await client.go
// Access data
console.log(user.data.name);
console.log(user.data.email);
`
`typescript
// Follow a link to related resource
const posts = await user.follow('posts').get();
// Follow with URI template parameters
const filtered = await user.follow('posts', { page: 2, size: 10 }).get();
// Chain navigation
const author = await posts.collection[0].follow('author').get();
`
`typescript
// POST - Create new resource
const newPost = await user.follow('posts').post({
data: { title: 'Hello World', content: 'My first post' },
});
// PUT - Full update
await post.put({
data: { title: 'Updated Title', content: 'Updated content' },
});
// PATCH - Partial update
await post.patch({
data: { title: 'New Title' },
});
// DELETE
await post.delete();
`
`typescriptBearer ${token}
// Add authentication
client.use(async (request, next) => {
request.headers.set('Authorization', );
return next(request);
});
// Add logging for specific origin
client.use(async (request, next) => {
console.log('Request:', request.url);
const response = await next(request);
console.log('Response:', response.status);
return response;
}, 'https://api.example.com');
`
`typescript
// GET requests are cached automatically
const user1 = await client.go
const user2 = await client.go
// Manual cache operations
resource.clearCache();
const cached = resource.getCache();
resource.updateCache(newState);
`
`typescript
const resource = client.go
// Listen for updates
resource.on('update', (state) => {
console.log('Resource updated:', state.data);
});
// Listen for stale events (after POST/PUT/PATCH/DELETE)
resource.on('stale', () => {
console.log('Cache is stale, refetch recommended');
});
// Listen for delete
resource.on('delete', () => {
console.log('Resource was deleted');
});
`
`typescript
const postsState = await user.follow('posts').get();
// Pagination metadata
console.log(Page ${postsState.data.page.number + 1} of ${postsState.data.page.totalPages});Total: ${postsState.data.page.totalElements} items
console.log();
// Iterate items
for (const post of postsState.collection) {
console.log(post.data.title);
}
// Navigate pages
const nextPage = await postsState.follow('next').get();
const prevPage = await postsState.follow('prev').get();
`
`typescript
import { Entity } from '@hateoas-ts/resource';
// Entity
type User = Entity<
// TData - resource properties
{ id: string; name: string; email: string },
// TLinks - available navigation links
{
self: User;
posts: Collection
'create-post': Post;
},
// TActions - HAL-Forms actions (optional)
{
'create-post': Post;
}
>;
`
`typescript
import { Collection } from '@hateoas-ts/resource';
// Collection automatically includes:
// - page: { size, totalElements, totalPages, number }
// - links: { first, prev, self, next, last }
type Posts = Collection
`
| Export | Type | Description |
| ------------------ | -------- | --------------------------------------- |
| createClient | Function | Create a HATEOAS client instance |Entity
| | Type | Define entity types with data and links |Collection
| | Type | Define paginated collection types |Resource
| | Class | Resource navigation and HTTP methods |ResourceRelation
| | Class | Relationship navigation |State
| | Type | Resource state with data and links |FetchMiddleware
| | Type | Request/response middleware type |
See @hateoas-ts/resource-react for React hooks:
`typescript
import { useResource, useInfiniteCollection } from '@hateoas-ts/resource-react';
function UserProfile({ userId }) {
const { data, loading, error } = useResource);
if (loading) return
if (error) return
return
Related Documentation
- Smart Domain DDD Architecture - Backend architecture design
- REST Principles and Agentic UI - REST architecture principles
Changelog
$3
- Direct HTTP methods:
.get(), .post(), .put(), .patch(), .delete()
- Request deduplication for concurrent requests
- Improved TypeScript generics$3
- React integration utilities (
@hateoas-ts/resource-react`)- Basic HAL resource navigation
- Type-safe entity definitions
- Cache management
- Event system
- Middleware support
MIT