Routes

Understand the default generated API routes by the Tensei REST plugin

Tensei automatically generates a fully featured and customisable GraphQL API for all your defined resources. This API would have GET, POST, PATCH and DELETE operations for each resource.

Routes

For each resource, a number of routes are generated. Let's take an example Comment resource. The comment resource has many Reactions.

import { tensei, resource, text, textarea, select, hasMany, belongsTo } from '@tensei/core'

export default tensei()
    .resources([
        resource('Comment')
            .fields([
                text('Title').sortable(),
                textarea('Content'),
                hasMany('Reaction')
            ]),

        resource('Reaction')
            .fields([
                select('Type')
                    .options([{ label: 'Heart', value: 'heart' }, { label: 'Downvote', value: 'downvote' }]),
                belongsTo('Comment')
            ])
    ])

The following routes will be generated for the Comment resource:

  • GET /api/comments fetches, filter and sort comments.
  • GET /api/comments/:id fetches a single comment by ID.
  • GET /api/comments/:id/:relatedResource fetches a relation to a single comment by ID.
  • POST /api/comments inserts a single comment to the database.
  • PATCH /api/comments/:id updates a single comment by ID.
  • DELETE /api/comments/:id deletes a single comment by ID from the database.

Fetch all resources

This endpoint may be used to fetch, filter and sort all records of a resource. For example, GET /api/comments fetches all comments in the database. The following is sample data returned from this endpoint:

{
    "data": [
        {
            "id": 1,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Officiis accusantium delectus voluptatem ea.",
            "content": "Quia pariatur sit iusto mollitia explicabo voluptatem adipisci odio maxime.",
        },
        {
            "id": 2,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "voluptatem adipisci odio maxime.",
            "content": "Quia pariatur sit iusto mollitia explicabo voluptatem adipisci odio maxime.",
        },
        {
            "id": 3,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Harum molestiae corrupti qui deserunt ipsam qui.",
            "content": "Harum molestunt ipsam qui.",
        },
        {
            "id": 4,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
            "content": "Aliquid qque nesciunt.",
        },
        {
            "id": 5,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Et saepe ad amet qui omnis aut voluptatem ut molestiae.",
            "content": "Et saepe auptatem ut molestiae.",
        }
    ],
    "meta": {
        "total": 1009,
        "page": 1,
        "per_page": 5,
        "page_count": 202
    }
}

Fetch single resource

This endpoint may be used to fetch a single record of a resource. For example, GET /api/comments/1 fetches a single comment in the database with ID of 1. The following is sample data returned from this endpoint:

{
    "data": {
        "id": 1,
        "created_at": "2020-12-07T17:47:19.000Z",
        "updated_at": "2020-12-07T17:47:19.000Z",
        "title": "Quasi libero rerum optio architecto quaerat.",
        "content": "um optio architecto quae Quasi libero rerum optio architecto quaerat.",
    }
}

This endpoint also supports selecting fields, and populating relationships.

This endpoint may be used to fetch relations of a single record. For example, GET /api/comments/1/reactions fetches all the reactions for a comment. This is a great way to fetch related records for large data sets, since you can paginate, sort, select fields, and populate relationships the related resource.

Calling this endpoint returns the following data:

{
    "data": [
        {
            "id": 1,
            "created_at": "2020-12-11T15:37:58.000Z",
            "updated_at": "2020-12-11T15:37:58.000Z",
            "type": null,
            "comment": 1
        },
        {
            "id": 2,
            "created_at": "2020-12-11T15:38:17.000Z",
            "updated_at": "2020-12-11T15:38:17.000Z",
            "type": "heart",
            "comment": 1
        },
        {
            "id": 3,
            "created_at": "2020-12-11T15:38:23.000Z",
            "updated_at": "2020-12-11T15:38:23.000Z",
            "type": "downvote",
            "comment": 1
        },
        {
            "id": 4,
            "created_at": "2020-12-11T15:38:25.000Z",
            "updated_at": "2020-12-11T15:38:25.000Z",
            "type": "downvote",
            "comment": 1
        },
        {
            "id": 5,
            "created_at": "2020-12-11T15:38:26.000Z",
            "updated_at": "2020-12-11T15:38:26.000Z",
            "type": "downvote",
            "comment": 1
        },
        {
            "id": 6,
            "created_at": "2020-12-11T15:38:26.000Z",
            "updated_at": "2020-12-11T15:38:26.000Z",
            "type": "downvote",
            "comment": 1
        }
    ],
    "meta": {
        "total": 6,
        "page": 0,
        "per_page": 0,
        "page_count": 0
    }
}

Please note that if you call this endpoint with a one-to-one or many-to-one relationship, the data property will contain an object and not an array. For example, if we call GET /api/reactions/1/comment to get the comment related to the resource with the ID of 1.

{
    "data": {
        "id": 1,
        "created_at": "2020-12-07T17:47:19.000Z",
        "updated_at": "2020-12-07T17:47:19.000Z",
        "title": "Quasi libero rerum optio architecto quaerat.",
        "content": "Praesentium facilis ab perspiciatis quidem.",
    }
}

Insert resource

This endpoint may be used to insert resource records into the database. For example, to create a comment, we may use the POST /api/comments endpoint.

POST /api/comments?populate=reactions.comment HTTP/1.1
Host: localhost:8810
Content-Type: application/json

{
	"title": "ero rerum optio architecto quaera",
	"content": "sentium facilis ab perspiciatis quide"
}

Notice that this endpoint supports populating relationships. You may pass parameters to populate the newly created record.

Update resource

This endpoint may be used to update a resource by ID. For example, to update a comment of ID 1, we may use the PATCH /api/comments/1 endpoint:

PATCH /api/comments/1?populate=reactions.comment HTTP/1.1
Host: localhost:8810
Content-Type: application/json

{
	"title": "ero rerum optio architecto quaera",
	"content": "sentium facilis ab perspiciatis quide"
}

Notice that this endpoint supports populating relationships. You may pass parameters to populate the record once it is updated.

Delete resource

This endpoint may be used to delete a resource by ID. For example, to delete a comment of ID 1, we may use the DELETE /api/comments/1 endpoint:

DELETE /api/comments/101?populate=reactions.comment HTTP/1.1
Host: localhost:5000
Content-Type: application/json

This endpoint supports populating relationships. You may pass parameters to populate the deleted record. You'll get a snapshot of the record before it is removed from the database.

Query Parameters

Pagination

Pagination is a great way to control the amount of data pulled out of your database at a time. You may paginate this endpoint using the per_page and page query parameters. To find a maximum of 5 comments offsetting the data by 10, you may use the following url and parameters:

http://localhost:8810/api/comments?page=2&per_page=5

Selecting Fields

Sometimes you may wish to get only specific fields from a resource. To do this, you may use the fields query parameter, with a comma separated list of all the fields you want returned:

http://localhost:8810/api/comments?fields=title

The following data should be returned from the API:

{
    "data": [
        {
            "id": 1,
            "title": "Aliquid quos iure et consequatur itaque nesciunt."
        },
        {
            "id": 2,
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
        },
        {
            "id": 3,
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
        },
        {
            "id": 4,
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
        },
        {
            "id": 5,
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
        }
    ],
    "meta": {
        "total": 1009,
        "page": 1,
        "per_page": 5,
        "page_count": 202
    }
}

Populating relationships

You may need to populate relationship data when fetching data. By default, no relationship data is populated. To populate a list of relationships, you may pass the populate query parameter, with a comma separated list of all relationships you want to populate:

http://localhost:8810/api/comments?populate=reactions

This will efficiently fetch all reactions related to the comments returned from this API call.

{
    "data": [
        {
            "id": 1,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
            "content": "Aliquid qque nesciunt.",
            "reactions": [
                {
                    "id": 1,
                    "created_at": "2020-12-11T15:37:58.000Z",
                    "updated_at": "2020-12-11T15:37:58.000Z",
                    "type": null,
                    "comment": 1
                },
                {
                    "id": 2,
                    "created_at": "2020-12-11T15:38:17.000Z",
                    "updated_at": "2020-12-11T15:38:17.000Z",
                    "type": "heart",
                    "comment": 1
                },
                {
                    "id": 3,
                    "created_at": "2020-12-11T15:38:23.000Z",
                    "updated_at": "2020-12-11T15:38:23.000Z",
                    "type": "downvote",
                    "comment": 1
                },
                {
                    "id": 4,
                    "created_at": "2020-12-11T15:38:25.000Z",
                    "updated_at": "2020-12-11T15:38:25.000Z",
                    "type": "downvote",
                    "comment": 1
                },
                {
                    "id": 5,
                    "created_at": "2020-12-11T15:38:26.000Z",
                    "updated_at": "2020-12-11T15:38:26.000Z",
                    "type": "downvote",
                    "comment": 1
                },
                {
                    "id": 6,
                    "created_at": "2020-12-11T15:38:26.000Z",
                    "updated_at": "2020-12-11T15:38:26.000Z",
                    "type": "downvote",
                    "comment": 1
                }
            ]
        },
        {
            "id": 2,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
            "content": "Aliquid qque nesciunt.",
            "reactions": []
        },
        {
            "id": 3,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
            "content": "Aliquid qque nesciunt.",
            "reactions": []
        },
        {
            "id": 4,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
            "content": "Aliquid qque nesciunt.",
            "reactions": []
        },
        {
            "id": 5,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Aliquid quos iure et consequatur itaque nesciunt.",
            "content": "Aliquid qque nesciunt.",
            "reactions": []
        }
    ],
    "meta": {
        "total": 1009,
        "page": 1,
        "per_page": 5,
        "page_count": 202
    }
}

You may also populate nested relationships by adding a . separator in the populate relationships passed. For example, for each reaction, you may want to in turn populate the comment. To achieve this, you'll use the following URL:

http://localhost:8810/api/comments?populate=reactions.comment

The data returned from the above query would be:

{
    "data": [
        {
            "id": 1,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Quasi libero rerum optio architecto quaerat.",
            "reactions": [
                {
                    "id": 1,
                    "created_at": "2020-12-11T15:37:58.000Z",
                    "updated_at": "2020-12-11T15:37:58.000Z",
                    "type": null,
                    "comment": {
                        "id": 1,
                        "created_at": "2020-12-07T17:47:19.000Z",
                        "updated_at": "2020-12-07T17:47:19.000Z",
                        "title": "Quasi libero rerum optio architecto quaerat."
                    }
                },
                {
                    "id": 2,
                    "created_at": "2020-12-11T15:38:17.000Z",
                    "updated_at": "2020-12-11T15:38:17.000Z",
                    "type": "heart",
                    "comment": {
                        "id": 1,
                        "created_at": "2020-12-07T17:47:19.000Z",
                        "updated_at": "2020-12-07T17:47:19.000Z",
                        "title": "Quasi libero rerum optio architecto quaerat."
                    }
                },
                {
                    "id": 3,
                    "created_at": "2020-12-11T15:38:23.000Z",
                    "updated_at": "2020-12-11T15:38:23.000Z",
                    "type": "downvote",
                    "comment": {
                        "id": 1,
                        "created_at": "2020-12-07T17:47:19.000Z",
                        "updated_at": "2020-12-07T17:47:19.000Z",
                        "title": "Quasi libero rerum optio architecto quaerat."
                    }
                },
                {
                    "id": 4,
                    "created_at": "2020-12-11T15:38:25.000Z",
                    "updated_at": "2020-12-11T15:38:25.000Z",
                    "type": "downvote",
                    "comment": {
                        "id": 1,
                        "created_at": "2020-12-07T17:47:19.000Z",
                        "updated_at": "2020-12-07T17:47:19.000Z",
                        "title": "Quasi libero rerum optio architecto quaerat."
                    }
                },
                {
                    "id": 5,
                    "created_at": "2020-12-11T15:38:26.000Z",
                    "updated_at": "2020-12-11T15:38:26.000Z",
                    "type": "downvote",
                    "comment": {
                        "id": 1,
                        "created_at": "2020-12-07T17:47:19.000Z",
                        "updated_at": "2020-12-07T17:47:19.000Z",
                        "title": "Quasi libero rerum optio architecto quaerat."
                    }
                },
                {
                    "id": 6,
                    "created_at": "2020-12-11T15:38:26.000Z",
                    "updated_at": "2020-12-11T15:38:26.000Z",
                    "type": "downvote",
                    "comment": {
                        "id": 1,
                        "created_at": "2020-12-07T17:47:19.000Z",
                        "updated_at": "2020-12-07T17:47:19.000Z",
                        "title": "Quasi libero rerum optio architecto quaerat."
                    }
                }
            ]
        },
        {
            "id": 2,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Cum est dolor.",
            "reactions": []
        },
        {
            "id": 3,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Eos veniam explicabo tempora ullam modi sint sit tenetur.",
            "reactions": []
        },
        {
            "id": 4,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Et nam earum qui quo sapiente sed.",
            "reactions": []
        },
        {
            "id": 5,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Cupiditate necessitatibus quo qui hic ea libero ut.",
            "reactions": []
        }
    ],
    "meta": {
        "total": 1009,
        "page": 1,
        "per_page": 5,
        "page_count": 202
    }
}

Populating using this method fetches all related data, and we recommend using this for small to medium sized data sets only. If you have a large data set, please visit the related resource documentation.

Sorting

Sometimes you may wish to sort the database records by one or more fields in specific orders. To do this, you may use the sort query parameter, passing the field and direction as shown below:

http://localhost:8810/api/comments?sort=title:asc,created_at:desc

The data returned should be sorted as shown below:

{
    "data": [
        {
            "id": 48,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "A dolor nemo atque tenetur voluptatem dolorem alias saepe."
        },
        {
            "id": 427,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "A et natus corporis aut reiciendis et."
        },
        {
            "id": 571,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "A rerum velit inventore laboriosam est cumque vel assumenda."
        },
        {
            "id": 782,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Ab cupiditate in vel."
        },
        {
            "id": 955,
            "created_at": "2020-12-07T17:47:20.000Z",
            "updated_at": "2020-12-07T17:47:20.000Z",
            "title": "Ab velit animi iure occaecati qui provident molestiae cumque ducimus."
        }
    ],
    "meta": {
        "total": 1009,
        "page": 1,
        "per_page": 5,
        "page_count": 202
    }
}

Filtering

Sometimes you want to filter database records by some query condition. You may use the where query parameter to provide a fully descriptive filtering criteria. This parameter should be a stringified object. Here's an example query filtering all comments with titles that match some text:

http://localhost:8810/api/comments?where[title][_like]=%tenetur%

The results returned will filter only records with title matching this text:

{
    "data": [
        {
            "id": 48,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "A dolor nemo atque tenetur voluptatem dolorem alias saepe."
        },
        {
            "id": 407,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Commodi tenetur consectetur fugiat ullam."
        },
        {
            "id": 476,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Consequatur tenetur natus et est in."
        },
        {
            "id": 3,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Eos veniam explicabo tempora ullam modi sint sit tenetur."
        },
        {
            "id": 135,
            "created_at": "2020-12-07T17:47:19.000Z",
            "updated_at": "2020-12-07T17:47:19.000Z",
            "title": "Est enim maxime tenetur."
        }
    ],
    "meta": {
        "total": 25,
        "page": 1,
        "per_page": 5,
        "page_count": 5
    }
}

Here are the possible filters you can use:

  where[field][_eq]
  where[field][_in]
  where[field][_nin]
  where[field][_gt]
  where[field][_gte]
  where[field][_lt]
  where[field][_lte]
  where[field][_ne]
  where[field][_not]
  where[field][_like]
  where[field][_re]
  where[field][_ilike] // postgres only
  where[field][_overlap] // postgres only
  where[field][_contains] // postgres only
  where[field][_contained] // postgres only

You may also pass in _and and _or for multiple filter criteria. Here's an example to filter by title, or by certain ids:

http://localhost:8810/api/comments?where[_or][0][title][_like]=%25tenetur%25&where[_or][1][id][_in][0]=1&where[_or][1][id][_in][1]=2&where[_or][1][id][_in][2]=3&where[_or][1][id][_in][3]=4&where[_or][1][id][_in][4]=5&where[_or][1][id][_in][5]=6&where[_or][1][id][_in][6]=7&where[_or][1][id][_in][7]=8&where[_or][1][id][_in][8]=9&where[_or][1][id][_in][9]=10'

The query becomes really complex to write when there are too many conditions. We recommend using the lightweight qs library to easily stringify queries before making API calls. The example query above becomes really easy to understand when written like this:

import qs from 'qs'

const query = qs.stringify({
    where: {
        _or: [
            {
                title: {
                    _like: '%tenetur%',
                },
            },
            {
                id: {
                    _in: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                },
            },
        ],
    },
}, { encodeValuesOnly: true })

axios.get(`http://localhost:8810/api/comments?${query}`)