Resources
Resources are created using the Resource class, which is a transformation layer responsible for shaping a single domain object into a predictable JSON response structure.
It does not merely wrap data — it defines how raw data becomes API output.
A Resource:
- Accepts a single object (plain object, model instance, DTO, etc.)
- Controls how that object is serialized
- Wraps the transformed output inside a standardized
{ data: ... }envelope - Supports attaching additional top-level fields
- Can behave like a Promise (it is awaitable)
- Is designed to be extended for custom transformation logic
Think of it as the boundary between your internal data structures and your public API contract.
Instead of returning raw objects from your application layer, a Resource ensures:
- Response consistency
- Centralized formatting logic
- Extensibility without mutating original data
- Clean separation between domain logic and presentation logic
When extended, the data() method becomes the canonical place where transformation rules live.
Creating a Resource
import { Resource } from 'resora';
const user = { id: 1, name: 'John' };
const resource = new Resource(user);Accessing Raw Data
resource.data();Returns the original resource payload unless overridden.
{ "id": 1, "name": "John" }JSON Response Format
Calling .json() prepares a structured response:
resource.getBody();Produces:
{
"data": {
"id": 1,
"name": "John"
}
}Arkormˣ Models
Resora detects Arkormˣ model instances automatically and serializes them through the model's toObject() output.
import { Resource } from 'resora';
const user = await User.query().findOrFail(1);
const body = new Resource(user).getBody();Result:
{
"data": {
"id": 1,
"name": "Jane Doe"
}
}Eager-loaded Arkormˣ relationships are also serialized recursively:
const user = await User.query().with(['profile', 'posts']).findOrFail(1);
const body = new Resource(user).getBody();{
"data": {
"id": 1,
"name": "Jane Doe",
"profile": {
"id": 10,
"bio": "Creator"
},
"posts": [
{ "id": 100, "title": "First" },
{ "id": 101, "title": "Second" }
]
}
}Adding Additional Data
You may attach extra top-level fields:
resource.additional({ status: 'success' }).getBody();Result:
{
"data": {
"id": 1,
"name": "John"
},
"status": "success"
}additional() is chainable.
Metadata Customization
Resources also support metadata customization via with() and withMeta().
- Use
with()for class-level metadata hooks. - Use
withMeta()for typed fluent metadata chaining.
See the full guide in Writing Resources - Metadata APIs: with() vs withMeta().
Response Customization
You can also customize:
- Payload key casing via
preferredCase - JSON envelope via
responseStructure - Final outgoing transport response via
withResponse()
See:
Building a Response Object
const response = resource.response(res);This returns a response-compatible object. The structure is framework-agnostic.
Thenable Support (Async/Await)
Resource is promise-like.
const result = await resource;Resolves to:
{
"data": {
"id": 1,
"name": "John"
}
}This allows usage like:
return await new UserResource(user);Extending Resource
Resources are meant to be extended.
Custom Transformation
class UserResource extends Resource {
data() {
return {
id: this.id,
name: this.name,
custom: 'data',
};
}
}new UserResource({ id: 1, name: 'John' }).data();Result:
{
"id": 1,
"name": "John",
"custom": "data"
}Accessing Original Payload
Inside extended classes:
this.toObject()returns original payload- Properties are proxied (
this.id,this.name)
Nesting Resources and Collections
You can compose resources just like any other transformation layer. A parent Resource may return:
- another
Resource - a
ResourceCollection - the result of
.toObject()from a nested resource or collection
Nested Collection Instance
Returning a collection instance directly is supported and keeps the parent resource code concise:
class FamilyMemberResource extends Resource {
data() {
return {
id: this.id,
fullName: `${this.firstName} ${this.lastName}`,
};
}
}
class FamilyMemberCollection extends ResourceCollection {
collects = FamilyMemberResource;
data() {
return this.toObject();
}
}
class FamilyOverviewResource extends Resource {
data() {
return {
id: this.id,
familyName: this.familyName,
members: new FamilyMemberCollection(this.members ?? []),
};
}
}Result:
{
"data": {
"id": 1,
"familyName": "Smiths",
"members": [
{
"id": 1,
"fullName": "Jane Doe"
}
]
}
}Nested Transformed Array with toObject()
You can also flatten the nested collection explicitly:
class FamilyOverviewResource extends Resource {
data() {
return {
id: this.id,
familyName: this.familyName,
members: new FamilyMemberCollection(this.members ?? []).toObject(),
};
}
}This produces the same output. Use this form when you want the transformed array value immediately for further composition inside data().
When to Use Which
- Return the collection instance when you want the clearest nested resource code.
- Call
.toObject()when you need the transformed array before returning it. - Use
collectson the nested collection so each item is transformed by the intended resource class.
Chaining Still Works
Extended resources retain:
.json().additional().response()awaitsupport