ORM: Resources
Introduction
When building an API, you may need a transformation layer that sits between your models and the JSON responses that are actually returned to your application's users. For example, you may wish to display certain attributes for a subset of users and not others, or you may wish to always include certain relationships in the JSON representation of your models. Athenna's resource classes allow you to expressively and easily transform your models and array of models into JSON.
Of course, you may always convert Athenna models or array of models to JSON using their toJSON
methods; however,
Athenna resources provide more granular and robust control over the JSON serialization of your models and their
relationships.
Generating resources
To generate a resource class, you may use the make:resource
Artisan command. By default, resources will be placed
in the app/Resources
directory of your application. Resources extend the Resource
class:
node artisan make:resource UserResource
Writing resources
Before you can use your resource class you will need to set up the static blueprint
method in your resource class:
import { Resource } from '@athenna/database'
export class UserResource extends Resource {
static blueprint(object) {
return {
id: object.id,
name: object.name,
email: object.email,
createdAt: object.createdAt,
updatedAt: object.updatedAt
}
}
}
Every resource class defines a static blueprint
method which returns the record of attributes that should be
converted to JSON when using the toJson
and toArray
methods of the Resource
class.
Using resources
Once the resource is defined, we can start using the toJson
method:
import { User } from '#app/Models/User'
import { UserResource } from '#app/Resources/UserResource'
const user = await User.find()
const json = UserResource.toJson(user)
You can also use the toArray
method to convert arrays of objects:
const users = await User.findMany()
const json = UserResource.toArray(users)
If the data mapped in your static blueprint
method has not been found in the object, it will be removed from the
resource:
const user = await User.query().select('id', 'name').find()
const json = UserResource.toJson(user) // { id: 1, name: 'Valmir Barbosa' }
If the value is present in your object, but its values is null
, it will be set in your object resource. Only the
undefined
values will be removed from your resources:
const user = await User.query().select('id', 'name', 'deletedAt').find()
user.testing = undefined
const json = UserResource.toJson(user)
// { id: 1, name: 'Valmir Barbosa', deletedAt: null }
tip
You are not limited to use resource classes only with models. You can use any type of object with the resources:
const objects = [{
name: 'Valmir Barbosa',
email: 'valmirphp@gmail.com'
}]
const json = UserResource.toArray(objects)
// [{ name: 'Valmir Barbosa', email: 'valmirphp@gmail.com' }]
The serialization of your resources will always depend on your business logic and creativity.
The toResource
method
The toResource
method will always be available in your models. By default, it will execute the toJSON
method, but
we recommend you to implement your toResource
method to make use of your Resource
class. Let's see in the above
example how to do it with the User
model:
import { Model } from '@athenna/database'
import { UserResource } from '#app/Resources/UserResource'
export class User extends Model {
toResource() {
return UserResource.toJson(this)
}
/*...*/
}
Now you can easily convert your User
model to a resource by calling the toResource
method:
const user = await User.find()
const json = user.toResource()
The toResource
method in arrays and collections
Athenna has extended the prototype of the JavaScript Array
class to have the toResource
method. This method
will always call the toResource
method of each item in your array if it exists:
const users = await User.findMany()
const json = users.toResource()
You can also use the toResource
method when using Athenna collections:
const usersCollection = await User.collection()
const json = usersCollection.toResource()
If you are using a different object that doesn't have the toResource
method, the itens in the array will not be
changed, basically, nothing will happen:
const objects = [{/*....*/}]
const theSameArray = objects.toResource()
Converting relationships
If you would like to include related resources in your json, you may load them first in the object that your resource's
static blueprint
method will use to build your json. In this example, we will use the PostResource
resource's
static toArray
method to add the user's blog posts to the resource:
import { Resource } from '@athenna/database'
import { PostResource } from '#app/Resources/PostResource'
export class UserResource extends Resource {
static blueprint(object) {
return {
id: object.id,
name: object.name,
email: object.email,
posts: PostResource.toArray(object.posts),
createdAt: object.createdAt,
updatedAt: object.updatedAt
}
}
}
Now we can simply use it this way:
const user = await User.query().with('posts').find()
const json = user.toResource()
warning
If you don't load your relationships before calling your resource class, it will be set as null
in the json when
using the toJson
method and set as an empty array when using the toArray
method:
// User without blog posts loaded
const user = await User.query()
.select('id', 'name', 'email')
.find()
const json = user.toResource()
// { id: 1, name: 'Valmir Barbosa', email: 'valmirphp@gmail.com', posts: [] }