Skip to main content
version 1.0.0

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: [] }