entity-routes logo
Docs

Introduction#

It's a way to generate a dedicated endpoint for an entity property.

Let's say you have a User entity that owns a collection of articles. Rather than exposing the articles in the /users route and have a lot of unnecessary nested data, you might want to directly access the article list with only the properties you want for that route scope.

Usage#

By decorating the articles property with the @Subresource decorator, you generates 3 routes :

  • GET:/users/:userId/articles, which will list all articles of the given user
  • POST:/users/:userId/articles, which will allow you to join an article to this user (a new article is inserted if no article.id is given !)
  • DELETE:/users/:userId/articles/:articleId, which will allow you to remove the relation between the given user/article

Let's implement that :

#
typescript
1@EntityRoute({ path: "/users", operations: CRUD_OPERATIONS })
2@Entity()
3class User {
4 @Column()
5 name: string;
7 @Subresource(() => Article)
8 articles: Article[];
9}
11@EntityRoute()
12@Entity()
13class Article {
14 @Column()
15 name: string;

Of course, you can customize your Subresource using SubresourceOptions as optional second argument.

Option keyTypeDefaultDescription
operationsSubresourceOperation["create", "list", "details", "delete"]Defines which routes should be generated for this subresource
maxDepthnumberundefinedRestrict the depth of subresources nesting allow from this one
canHaveNestedbooleantrueAllow this subresource to have nested child subresources
canBeNestedbooleantrueAllow this subresource to be used as a child subresource

Nesting#

Subresources are nestable by default, so you could have a GET:/users/:userId/articles/comments by making Article.comments a subresource as well. And if you're crazy enough, nothing stops you from nesting further with a GET:/users/:userId/articles/comments/upvotes, etc.

Max depth#

You can control the max depth using the maxDepth key of SubresourceOptions. Each maxDepth is independant from each other. If no max depth is defined, it will fallback to the defaultSubresourceMaxDepthLvl of EntityRouteOptions, and if somehow no defaultSubresourceMaxDepthLvl is defined, it will default to 2.

Circular#

Circular subresources are disabled by default. You can allow this behavior using the allowCircular key of SubresourceOptions.

By allowing circular subresources (and with the appropriates max depths configured), the route GET:/users/articles/writers would be generated.

With single relations#

The example above and most use-cases of subresources will be for collection relation (OneToMany/ManyToMany). But sometimes you might want a single relation (OneToOne/ManyToOne) to be a subresource.

There is actually no difference in usage between single/collection subresources but here's an example for the sake of it.

Let's say you have a User entity that is linked with a config relation. Rather than exposing the config in the /users route and have a lot of unnecessary nested data, you might want to directly access the config with only the properties you want for that route scope.

By decorating the config property with the @Subresource decorator, you generates 3 routes :

  • GET:/users/:userId/config, which will show you the config of the given user
  • POST:/users/:userId/config, which will allow you to join a config to this user (a new config is inserted if no config.id is given !)
  • DELETE:/users/:userId/config, which will allow you to remove the relation between the given user/config

Format in response#

With the EntityRouteOptions key shouldSetSubresourcesIriOnItem, the corresponding subresources IRI are added for each items having subresources (without exposing them through @Groups).

With subresource IRI#

Using the same example from above with User/Article, here's how that would look like :

#
typescript
1@EntityRoute({ path: "/users", operations: CRUD_OPERATIONS }, { shouldSetSubresourcesIriOnItem: true })
2@Entity()
3class User extends AbstractEntity {
4 @Groups({ user: ["create", "details"], article: ["list"] })
5 @Column()
6 name: string;
8 @Subresource(() => Article)
9 @OneToMany(() => Article, (article) => article.author)
10 articles: Article[];
13@EntityRoute()
14@Entity()
15class Article extends AbstractEntity {
16 @ManyToOne(() => User, (user) => user.articles)
17 author: User;
19 @Groups({ user: ["details"] })
20 @Column()
21 title: string;

The articles property will have its subresource IRI displayed in the responses :

#
json
1{
2 "@context": {
3 "operation": "details",
4 "entity": "user"
5 },
6 "articles": "/api/users/1/articles",
7 "id": 1,
8 "name": "Alex"
9}
GET:/users/1
The article property is an array of IRI

With relation exposed#

When exposing the articles property on user.details, the response will differ :

#
ts
1// ...
2class User extends AbstractEntity {
3 // ...
5 @Groups({ user: ["details"] })
6 @Subresource(() => Article)
7 @OneToMany(() => Article, (article) => article.author)
8 articles: Article[];
9}
#
json
1{
2 "@context": {
3 "operation": "details",
4 "entity": "user"
5 },
6 "articles": ["/api/articles/123"],
7 "id": 1,
8 "name": "Alex"
9}
GET:/users/1
The article property is now an array of article IRI

With nested property exposed#

And if you exposed properties from that subresource relation (with @Groups):

#
typescript
1// ...
2class User {
3 @Groups({ user: ["details"] })
4 @Subresource(() => Article)
5 @OneToMany(() => Article, (article) => article.author)
6 articles: Article[];
7}
9// ...
10class Article extends AbstractEntity {
11 @ManyToOne(() => User, (user) => user.articles)
12 author: User;
14 @Groups({ user: ["details"] })
15 @Column()
16 title: string;

This is how the response would look like with User.articles.title exposed :

#
json
1{
2 "@context": {
3 "operation": "details",
4 "entity": "user"
5 },
6 "articles": [
7 {
8 "id": 123,
9 "title": "First article"
10 }
11 ],
12 "id": 1,
13 "name": "Alex"
GET:/users/1
The article property is an array of object with every nested properties of article exposed

Metadata#

You can retrieve the RouteSubresourcesMeta registered by an @EntityRoute decorator by using the getRouteSubresourcesMetadata function.

Prev
Decorator functions
Next
Subresources Playground