The Restful API

The API lets us read, create, update and delete items. Generally, our endpoint (API-speak for URL) decides which collection or item we're referring to, and the HTTP verb describes whether we want to read (GET), create (POST), update (PUT) or delete (DELETE). Strict RESTful APIs also have PATCH and OPTIONS. JXP doesn't. A PUT is a PATCH.

Getting a document

To get a single document, we use the endpoint with the collection and document _id. Note that the data returns an object.

Request

GET /api/test/5eb7cf838c9fba641e0e9dcb

Response

{
    "data": {
        "_deleted": false,
        "shmack": [
            "do",
            "ray",
            "me"
        ],
        "array_link_id": [],
        "_id": "5eb7cf838c9fba641e0e9dcb",
        "foo": "Foo1",
        "bar": "Bar",
        "yack": {
            "yack": "yack",
            "shmack": 1
        },
        "fulltext": "In Xanadu did Kubla Khan a stately pleasure dome decree",
        "_owner_id": "5eb7cf838c9fba641e0e9dc3",
        "createdAt": "2020-05-10T09:55:15.957Z",
        "updatedAt": "2020-05-10T09:55:16.144Z",
        "__v": 0,
        "link_id": "5eb7cf848c9fba641e0e9dcc",
        "other_link_id": "5eb7cf848c9fba641e0e9dcd",
        "id": "5eb7cf838c9fba641e0e9dcb"
    }
}

Getting many documents

You can get all the documents from a collection by hitting the collection name endpoint. Note that the data returns an array.

Request

GET /api/test

Response

{
    "count": 1,
    "data": [
        {
            "_deleted": false,
            "shmack": [
            "do",
            "ray",
            "me"
            ],
            "array_link_id": [],
            "_id": "5eb7cf838c9fba641e0e9dcb",
            "foo": "Foo1",
            "bar": "Bar",
            "yack": {
            "yack": "yack",
            "shmack": 1
            },
            "password": "$2a$04$lhy1QmVrUc7gGF7TKPAGdePdGVw51YQRk1b9.JPxrlXR/IgPOyeSi",
            "fulltext": "In Xanadu did Kubla Khan a stately pleasure dome decree",
            "_owner_id": "5eb7cf838c9fba641e0e9dc3",
            "createdAt": "2020-05-10T09:55:15.957Z",
            "updatedAt": "2020-05-10T09:55:16.144Z",
            "__v": 0,
            "link_id": "5eb7cf848c9fba641e0e9dcc",
            "other_link_id": "5eb7cf848c9fba641e0e9dcd",
            "id": "5eb7cf838c9fba641e0e9dcb"
        }
    ]
}

Limit and pagination

We can limit the number of records returned by adding ?limit=<number of records>. When we add a limit, the response includes the limit, page_count, page, and next.

You can paginate with the page=<page number> parameter. Page count starts at 1.

If you go beyond the total number of pages, you will get an empty data array.

Default limit

When query_limits.enabled is true (default), list and query endpoints apply ?limit=100 if you omit limit. Use ?limit= up to the configured maximum (default 1000) for larger pages.

Large collections (explicit limit)

For collections with at least 10,000 documents (by default), you must pass an explicit ?limit= between 1 and the maximum. Omitting limit on a large collection returns 400 Bad Request (the default limit does not satisfy this rule).

Use GET /count/<model> for totals. Add ?count=true (or ?page=) when you need count / page_count in the list response without paginating.

Totals in list responses

By default, list responses omit count unless you pass ?count=true or ?page=, to avoid expensive countDocuments on every request.

Configure globally when starting JXP:

query_limits: {
  enabled: true,
  large_collection_threshold: 10000,
  max: 1000,
  default: 100,
  skip_count_unless_paginated: true,
}

Override per model in schema options:

const ReaderSchema = new JXPSchema({ ... }, {
  perms: { ... },
  query_limits: { large_collection_threshold: 5000, max: 500 },
});

Note If you have more than 100,000 items in your collection, count will return as -1, else doing a full-dataset filtered count becomes too expensive and can cause serious performance issues.

Counting

To get a count of a collection, use the endpoint /count/<model>. This works even on large collections that wouldn't otherwise return a count due to size of over 100,000 items. Filters can be applied, but search does not work.

Eg.

GET /count/test

Response:

{
    count: 1
}

Populating

This is one of the most useful features of this API. You can automatically populate the results with linked objects.

  • To autopopulate all the linked objects, use the parameter autopopulate=1

  • To populate just one linked object, use populate=linked_object

  • To populate multiple linked objects, you can use populate[]=linked_object1&populate[]=linked_object2

  • To populate and just return specific fields, use populate[linked_object]=field1,field2

Filtering

Use filter[fieldname]=blah to filter.

You can also filter for greater than, less than, greater than or equals and less than or equals for stuff like dates and numbers.

filter[start_date]=$gte:12345678 (Pro tip: use Unix time to filter on dates.)

You can also pass a Regex expression JXP will convert it to a real Regex for you: filter[name]=$regex:/blah/i.

Searching

This works like filtering, but it's a case-insensitive search:

search[email]=jason

Full-text searching

You can perform a search against a full-text index by using search.

search=Test

To ensure a full-text index across all fields on your model, add this to your schema:

MySchema.index( { "$**": "text" } );

See MongoDB Text Indexes for more options, such as weighted indexing.

Note that you can only declare one index per collection (and hence schema).

Saving a new document

POST always saves a new item, so the endpoint is always /api/{modelname}. For instance, POST /api/test would create a new test item.

Updating a document

PUT updates an existing item, so the endpoint needs to include the _id, as in /api/{modelname}/{_id}. Eg. PUT /api/test/5731a48b7571ff6248bd6d9c.

Deleting a document

As with PUT, we need to reference a specific item, so the endpoint needs to include the _id, as in /api/{modelname}/{_id}. Eg. DELETE /api/test/5731a48b7571ff6248bd6d9c. Note that we soft-delete documents. See Special Features - Soft deleting for more info.

Advanced queries

If you need to send an advanced query, such as a combined $and/$or, POST JSON to /query/{modelname} with a query object (required). Most URL features from GET /api/{modelname} still apply (limit, page, sort, populate, filter, autopopulate, fields) except search.

Eg.

{
    "query": {
        "$and": [
            {
                "foo": {
                    "$regex": "foo",
                    "$options": "i"
                }
            },
            {
                "bar": "Bar"
            }
        ]
    }
}

See Queries for more examples.

Aggregate queries

You can perform an aggregate query by POSTing a pipeline array to /aggregate/{modelname}, or wrapping it in { "query": [ ... ] }. See Aggregations.

Eg.

[
    { 
        $match: { 
            $or: [ 
                { 
                    score: { 
                        $gt: 70, $lt: 90 
                    } 
                }, 
                { 
                    views: { 
                        $gte: 1000 
                    } 
                } 
            ] 
        } 
    },
    { 
        $group: { 
            _id: null, 
            count: { 
                $sum: 1 
            } 
        } 
    }
]