Neurelo API Reference (REST)

Introduction

This API documentation is intended to provide an overview of how Neurelo generates APIs based on your data definition. It will go into detail on how to perform operations using a sample schema definition. You can then apply these concepts and patterns to your own schema. Reference documentation generated specifically for your data definition is included in your specific project on Neurelo's Data Access Platform.

Neurelo provides two types of APIs that are automatically generated and available for use.

  • The REST API has predictable resource-oriented URLs, accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.

  • The GraphQL API exposes a single endpoint where clients can send GraphQL queries or mutations and a strictly-typed schema that be be shared across consumers.

You can toggle between the GraphQL and REST API documentation when reviewing the documentation in the platform.

API Headers

Authentication

API calls to Neurelo are authenticated using an API Key for all API requests. This api key defines which environment the API request is interacting with. You can create an api key under the settings of the specific environment that you are trying to access. API Keys cannot be used across environments.

To utilize an api key, include it as a header with your API Request, using X-API-KEY as the key.

Query Strategy

Neurelo generated APIs can be configured to fetch data using either the selects or joins strategy. The main difference is that selects executes multiple queries behind the scenes to gather the desired data, whereas joins uses a single query when possible using database-level joins, to fetch the results. By default, joins strategy is enabled. This can be changed using an HTTP header.

For example, to fetch all the users and their corresponding posts using selects strategy:

curl https://$ENV_API_URL/rest/user
    --url-query select='{"$related":true}'
    --header 'X-READ-STRATEGY: selects'

Similarly, 'X-READ-STRATEGY: joins' can be used.

joins strategy is suitable for scenarios where you want to minimize the number of queries made to the database, especially when the relation expressions are relatively simpler, and there's less risk of data duplication.

selects can be faster in scenarios with multiple many-to-many or one-to-many relations. This is because the database's flat record list from joins can sometimes contain a significant amount of repeated information. Despite joins' quick execution in the database, the process of moving and parsing this redundant data into JSON can incur high costs.

Only GETcalls support this feature.

Query Viz

To review the database queries that Neurelo will be sending to your data source, you can use the X-QUERY-VIZ HTTP header. For example, to enable query viz while fetching all the users and their corresponding posts:

curl https://$ENV_API_URL/rest/user
    --url-query select='{"$related":true}'
    --header 'X-QUERY-VIZ: true'

This header will return both the query string and its corresponding query parameters.

Similarly, 'X-QUERY-VIZ: false' can be used to disable the viz. By default, this header is disabled.

IMPORTANT: When query viz is enabled, the queries will still run against your selected environment, and they may modify data in that environment depending on your queries.

Operations

Creating Objects

You can create objects by making a POST request to the /rest/object, where object is your object’s name. The request body should be a JSON array of objects representing the objects that you are looking to insert.

In this example, we will use a sample schema with a User object and a BlogPost object. A user has many posts, and a post has one user.

Creating one object (CreateInput)

To create a single user, assuming that we have the sample schema running in an environment, we can issue a POST request to /rest/user/__one with the body set to a User object. For example

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{"$scalars":true}'
    --json '{name: "Admin User", country: "United States"}'

The response from this insert request will look like this, which is the object that was inserted:

{
  "data": {
    "id": 1,
    "name": "Admin User",
    "country": "United States"
  }
}

Creating multiple objects (CreateManyInput)

To create multiple users, assuming that we have the sample schema running in an environment, we can issue a POST request to /rest/user with the body set to an array of User objects. For example:

curl -X POST https://$ENV_API_URL/rest/user
    --json '[{name: "User 1", country: "Canada"}, {name: "User 2", country: "United States"}]'

The response (AffectedRowsOutput) from this insert request will look like this, which is the count of the inserted rows:

{
  "data": {
    "count": 2
  }
}

Currently, related objects cannot be created or associated with the 'root' object using CreateManyInput. To achieve this, it is necessary to use CreateInput.

Creating objects with relations

Currently, Neurelo for MongoDB does not directly support the creation of objects with relations. However, you can create inner objects (which are equivalent to embedded documents, meaning documents nested within another document) or use references to accommodate your data modeling needs. Refer to the Modeling Relationships for MongoDB with Inner Objects and References section for more information.

When creating a single object, you can create or connect related object in the same transaction.

On an object with a has one relationship to another object, you can use the create input to create a new related object automatically associated with the 'root' object. Alternatively, you can use the connect input to link an existing related object to the 'root' object. When using connect, an error will be thrown if the linked object is not found. To address this, use connectOrCreate, which links an existing related object to the 'root' object or creates a new related object if it doesn't exist.

On an object with a has many relationship to another object, you can also utilize create, connect, and connectOrCreate. Additionally, you can use createMany to create many related objects.

create

To demonstrate creating a root object and its related object, we will use a sample schema with a User object and a BlogPost object. A user has many posts, and a post has one user.

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User", country: "United States", posts: { create: [{ title: "Post Title" }] }}'

The response from this insert request will look like this, which is the inserted object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User",
    "posts": [
      {
        "id": 1,
        "title": "Post Title"
      }
    ]
  }
}

connect

To create a root object and link it with an existing related object in a has many relationship, assuming that we have the sample schema running in an environment, we can use the connect input.

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "User 1", country: "Canada", posts: { connect: [{ id: 2 }] }}'

The response from this insert request will look like this, which is the inserted object(s):

{
  "data": {
    "id": 1,
    "name": "User 1",
    "country": "Canada",
    "posts": [
      {
        "id": 2,
        "title": "My Trip to Hawaii"
      }
    ]
  }
}

In the above example, the Post with an ID of 2 previously existed and is now being associated with a relationship with the new root object.

connectOrCreate

To create a root object and link it with an existing related object or create a new related object if it doesn't exist (when the root and related object are in a has many relationship), assuming that we have the sample schema running in an environment, we can use the connectOrCreate input. For example,

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "User 1", country: "Canada", posts: { connectOrCreate: [{ create: {"title": "My thoughts on Wonderwall by Oasis"}, where: {"id": 30} }] }}'

The response from this insert request will look like this, which is the inserted object(s):

{
  "data": {
    "id": 1,
    "name": "User 1",
    "country": "Canada",
    "posts": [
      {
        "id": 3,
        "title": "My thoughts on Oasis' Wonderwall"
      }
    ]
  }
}

In the above example, by observing the Post's ID, we can conclude that the ID 30 was not found in the Post object. As a result, a new object was created.

createMany

To create a root object and link it with many related objects in a has many relationship, assuming that we have the sample schema running in an environment, we can use the createMany input.

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "User 1", country: "United States", posts: { createMany: [{"title": "Watching a rooftop concert"}, {"title": "How to buy concert tickets?"}] }}'

The response from this insert request will look like this, which is the inserted object(s):

{
  "data": {
    "id": 1,
    "name": "User 1",
    "country": "United States",
    "posts": [
      {
        "id": 1,
        "title": "Watching a rooftop concert"
      },
      {
        "id": 2,
        "title": "How to buy concert tickets?"
      }
    ]
  }
}

Moreover, to prevent the insertion of nested records with unique fields or identifiers that already exist, we can use skipDuplicates. For example:

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User", country: "Canada", posts: {createMany: {data: [{id: 3, name: "Post 1"}, {id: 3, name: "Post 2"}], skipDuplicates: true}}}'

The response from this insert request will look like this, which is the inserted object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User",
    "country": "Canada",
    "posts": [
      {
        "id": 3,
        "title": "Post 1"
      }
    ]
  }
}

In the above example, as Post IDs were same, the second nested record was prevented from being inserted.

Currently, skipDuplicates is not supported for MongoDB.

Retrieving Objects

You can retrieve objects by making a GET request to the /rest/object, where object is your object’s name. By default, this endpoint will return all objects, you can utilize the parameters described above to filter and limit your results.

In this example, we will use a sample schema with a User object and a BlogPost object. A user has many posts, and a BlogPost has one user. A user has a relationship property to posts called posts and the post has a relationship property to users called author.

Retrieving unique object

To retrieve a unique user, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/user/{{userId}}. For example:

curl https://$ENV_API_URL/rest/user/1

The response from this request will look like this, which is the retrieved object:

{
  "data": {
    "id": 1,
    "name": "User 1",
    "country": "Canada"
  }
}

Retrieving all objects

To retrieve all users, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/user. For example:

curl https://$ENV_API_URL/rest/user

The response from this request will look like this, which is an array of the retrieved objects:

{
  "data": [
    {
      "id": 1,
      "name": "User 1",
      "country": "Canada"
    },
    {
      "id": 2,
      "name": "User 2",
      "country": "United States"
    }
  ]
}

Filtering for specific objects

To filter specific objects, we can utilize the filter query parameter. For complete documentation of all the options available to you with the query parameter, take a look at the filter parameter documentation below.

To filter all specific objects, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/user with the filter parameter specified. For example:

curl https://$ENV_API_URL/rest/user
    --url-query filter='{"country":{"contains":"United"}}'

The response from this request will look like this, which is an array of the retrieved objects:

{
  "data": [
    {
      "name": "User 2",
      "country": "United States"
    }
  ]
}

To retrieve all related objects, we can utilize the select query parameter along with the $related parameter. Furthermore, to retrieve all objects, $scalars parameter can be used. When $scalars and $related is used in conjunction, all the retrieved objects and their related objects can be queried. For a complete documentation of all the options available to you with the query parameter, take a look at the select parameter documentation below.

To retrieve all users with their posts, assuming that we have the sample schema running in an environment, we can issue a GET request to rest/user with the select parameter specified. For example:

curl https://$ENV_API_URL/rest/user
    --url-query select='{"$scalars":true, "$related":true}'

The response from this request will look like this, which is an array of the retrieved objects and their related objects:

{
  "data": [
    {
      "name": "Admin User",
      "country": "United States",
      "posts": [
        {
          "name": "My Trip to Egypt",
          "words": 1200
        },
        {
          "name": "My Trip to Canada",
          "words": 800
        }
      ]
    }
  ]
}

To retrieve specific related objects, we can utilize the select query parameter and manually specify the objects. For a complete documentation of all the options available to you with the query parameter, take a look at the select parameter documentation below.

To retrieve all the users and their post names, assuming that we have the sample schema running in an environment, we can issue a GET request to rest/user with the select parameter specified. For example:

curl https://$ENV_API_URL/rest/user
    --url-query select='{"$scalars":true, "posts": {"name": true}}'

The response from this request will look like this, which is an array of the retrieved objects and their specific related object:

{
  "data": [
    {
      "name": "Admin User",
      "country": "United States",
      "posts": [
        {
          "name": "My Trip to Egypt"
        },
        {
          "name": "My Trip to Canada"
        }
      ]
    }
  ]
}

To retrieve n-levels of related objects, we can utilize the select query parameter and manually specify the deeply-nested related objects. For a complete documentation of all the options available to you with the query parameter, take a look at the select parameter documentation below.

Assume that in our sample schema, there is a new object Activity, such that each post has many activities. A post has a relationship property to activities called activity.

To retrieve all the users, their post names, and activities on each post, assuming that we have the sample schema running in an environment, we can issue a GET request to rest/user with the select parameter specified. For example:

curl https://$ENV_API_URL/rest/user
    --url-query select='{"$scalars":true, "posts": {"name": true, "activity": true}}'

The response from this request will look like this, which is an array of the retrieved objects and their specific related object:

{
  "data": [
    {
      "name": "Admin User",
      "country": "United States",
      "posts": [
        {
          "name": "My Trip to Egypt",
          "activity": [
            {
              "name": "User 123",
              "action": "Like"
            },
            {
              "name": "User 234",
              "action": "Bookmark"
            }
          ]
        },
        {
          "name": "My Trip to Canada",
          "activity": [
            {
              "name": "User 345",
              "action": "Like"
            }
          ]
        }
      ]
    }
  ]
}

Updating Objects

You can update objects by making a PATCH request to the /rest/object, where object is your object’s name. The request body should be a JSON object with the properties that you are looking to update, and you can use the filter parameter to apply your desired condition to the operation.

Updating unique object (UpdateInput)

To update a unique user, assuming that we have the sample schema running in an environment, we can issue a PATCH request to /rest/user/{{userId}}. For example:

curl -X PATCH https://$ENV_API_URL/rest/user/1
      --json '{name: "Admin User 1"}'

The response from this request will look like this, which is the updated object:

{
  "data": {
    "id": 3,
    "name": "Admin User 1",
    "country": "Canada"
  }
}

Updating multiple objects (UpdateManyInput)

To update multiple users, assuming that we have the sample schema running in an environment, we can issue a PATCH request to /rest/user. For example:

curl -X PATCH https://$ENV_API_URL/rest/user
    --url-query filter='{"country":{"contains":"United"}}'
    --json '{name: "Admin User 1"}'

The response (AffectedRowsOutput) from this update request will look like this, which is the count of the updated rows:

{
  "data": {
    "count": 1
  }
}

Updating scalars using special operations

increment, decrement, multiply, divide

When using the integer type for a property, the UpdateInput and UpdateManyInput provides basic arithmetic capabilities through increment, decrement, multiply, and divide. The increment operation increases the specific property by a given number, while decrement decreases it. Similarly, multiply and divide multiply or divide the property value by a specified number. This functionality proves useful by eliminating the need for an application-level read before a write call. An example of this is as follows:

curl -X PATCH https://$ENV_API_URL/rest/post/1
      --json '{name: "My updated .bashrc file", words: {increment: 30}}'

The response from this request will look like this, which is the updated object:

{
  "data": {
    "id": 1,
    "name": "My updated .bashrc file",
    "words": 350
  }
}

push

When using the array type for a property, the UpdateInput and UpdateManyInput provides a push operation. This inserts a value or array of values at the end of the array. If the value is null, it would initialize the array with the value(s) provided. For example,

curl -X PATCH https://$ENV_API_URL/rest/post/1
      --json '{name: "My updated .bashrc file", tags: {push: ["alias"]}}'

The response from this request will look like this, which is the updated object:

{
  "data": {
    "id": 1,
    "name": "My updated .bashrc file",
    "tags": ["termcolor", "alias"]
  }
}

Updating with relations

Currently, Neurelo for MongoDB does not directly support the updation of objects with relations. However, you can update inner objects (which are equivalent to embedded documents, meaning documents nested within another document) or use references to accommodate your data modeling needs. Refer to the Modeling Relationships for MongoDB with Inner Objects and References section for more information.

When updating a single object, you can create, update, disconnect or delete related object in the same transaction.

On an object with a has one relationship to another object, you can use the update input to update a related object automatically associated with the 'root' object. Alternatively, you can use the disconnect input to unlink an existing related object with the 'root' object. When using update, an error will be thrown if the linked object is not found. To address this, use upsert, which updates an existing related object to the 'root' object or creates a new related object if it doesn't exist. You can also use delete input to delete an existing related object.

The connect, connectOrCreate, and create inputs (as explained in the Creating objects with relations section) are also supported here.

On an object with a has many relationship to another object, you can also utilize create, connect, connectOrCreate, update, upsert, delete, and disconnect inputs. Additionally, you can use createMany, updateMany, and deleteMany to create, update, and delete many related objects, respectively.

createMany is explained in the Creating objects with relations section.

update

To demonstrate updating a root object and its related object, we will use a sample schema with a User object and a BlogPost object. A user has many posts, and a post has one user.

curl -X PATCH https://$ENV_API_URL/rest/user/1
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User 1", posts: { update: [{ where: {id: 2}, data: {name: "My system configuration" } }] } }'

The response from this update request will look like this, which shows the updated object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User 1",
    "posts": [
      {
        "id": 1,
        "name": "Test"
      },
      {
        "id": 2,
        "name": "My system configuration"
      }
    ]
  }
}

In the above example, the Post with an ID of 2 previously existed and is now being updated along with the name of the user.

upsert

To update a root object and update its corresponding related object or create a new related object if it doesn't exist (when the root and related object are in a has many relationship), assuming that we have the sample schema running in an environment, we can use the upsert input. For example,

curl -X PATCH https://$ENV_API_URL/rest/user/1
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User 1", posts: { upsert: { where: {"id": 2}, update: {name: "Hardware Info" }, create: {name: "Hardware Info"} } } }'

The response from this upsert request will look like this, which is the upsert object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User 1",
    "posts": [
      {
        "id": 1,
        "name": "Test"
      },
      {
        "id": 2,
        "name": "Hardware Info"
      }
    ]
  }
}

disconnect

To unlink an existing related object with the 'root' object (when the root and related object are in a has many relationship), assuming that we have the sample schema running in an environment, we can use the disconnect input. For example,

curl -X PATCH https://$ENV_API_URL/rest/user/
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User 1", posts: { disconnect: { id: 2 } } }'

The response from this request will look like this, which is the updated object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User 1",
    "posts": [
      {
        "id": 1,
        "name": "Test"
      }
    ]
  }
}

In the above example, the Post with an ID of 2 previously existed and still exists, but it is now being unlinked with respect to the 'root' object. Furthermore, the name of the user also gets updated.

delete

To delete an existing related object asccociated with a 'root' object (when the root and related object are in a has many relationship), assuming that we have the sample schema running in an environment, we can use the delete input. For example,

curl -X PATCH https://$ENV_API_URL/rest/user/
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User 1", posts: { delete: { id: 2 } } }'

The response from this request will look like this, which is the updated object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User 1",
    "posts": [
      {
        "id": 1,
        "name": "Test"
      }
    ]
  }
}

In the above example, the Post with an ID of 2 is now deleted. Furthermore, the name of the user also gets updated.

updateMany

To update a root object and its many related objects in a has many relationship, assuming that we have the sample schema running in an environment, we can use the updateMany input.

curl -X PATCH https://$ENV_API_URL/rest/user/
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User 1", posts: {updateMany: [{where: {id: 1}, data: {name: "Test Blog"}}, {where: {id: 2}, data: {name: "Hardware Info Blog"}}]}}'

The response from this updateMany request will look like this, which is the updated object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User 1",
    "posts": [
      {
        "id": 1,
        "name": "Test Blog"
      },
      {
        "id": 2,
        "name": "Hardware Info Blog"
      }
    ]
  }
}

deleteMany

To update a root object and delete its many related objects in a has many relationship, assuming that we have the sample schema running in an environment, we can use the deleteMany input.

curl -X PATCH https://$ENV_API_URL/rest/user/
    --url-query select='{ "$scalars": true, "posts": { "$scalars": true }}'
    --json '{name: "Admin User Empty", posts: {deleteMany: [{id: 1}, {id: 2}]}}'

The response from this deleteMany request will look like this, which is the deleted object(s):

{
  "data": {
    "id": 1,
    "name": "Admin User Empty",
    "posts": []
  }
}

Deleting Objects

You can delete objects by making a DELETE request to the /rest/object, where object is your object’s name. You can use the filter parameter to apply your desired condition to the operation.

In this example, we will use a sample schema with a User object.

Delete unique object

To delete a unique user, assuming that we have the sample schema running in an environment, we can issue a DELETE request to /rest/user/{{userId}}. For example:

curl -X DELETE https://$ENV_API_URL/rest/user/1

The response from this request will look like this, which is the deleted object:

{
  "data": {
    "id": 1,
    "name": "User 1",
    "country": "Canada"
  }
}

Delete all objects

To delete all users, assuming that we have the sample schema running in an environment, we can issue a DELETE request to /rest/user. For example:

curl -X DELETE https://$ENV_API_URL/rest/user

The response from this request will look like this, which is the count of the deleted object(s):

{
  "data": {
    "count": 1
  }
}

Delete specific objects

To delete specific objects, we can utilize the filter query parameter. For complete documentation of all the options available to you with the query parameter, take a look at the filter parameter documentation below.

To delete all specific Users, assuming that we have the sample schema running in an environment, we can issue a DELETE request to /rest/user with the filter parameter specified. For example:

curl -X DELETE https://$ENV_API_URL/rest/user
      --url-query filter="{\"name\":{\"contains\":\"Admin\"}}"

The response from this request will look like this, which is the count of the deleted object(s):

{
  "data": {
    "count": 1
  }
}

Aggregating Objects

You can aggregate objects by making a GET request to the /rest/object/__aggregate, where object is your object’s name. The select query parameter is required to specify the fields to be aggregated. The following aggregate functions are supported: _count, _sum, _avg, _min, and _max.

In this example, we will use a sample schema with a Post object.

Aggregating specific objects

To aggregate specific objects, we can utilize the select query parameter. For a complete documentation of all the options available to you with the query parameter, take a look at the select parameter documentation below.

To average specific objects, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/post/__aggregate with the _avg query option specified for the select query parameter. This query option takes an array of fields to be aggregated. For example:

curl https://$ENV_API_URL/rest/post/__aggregate
    --url-query select='{"_avg": ["words"]}'

The response (AggregateOutput) from this request will look like this, which contains the aggregated value:

{
  "data": {
    "_avg": {
      "words": 1000.0
    }
  }
}

Aggregating all objects

Presently, only _count option supports aggregating all objects.

To count all objects, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/post/__aggregate with the _count query option and _all value specified for the select query parameter. For example:

curl https://$ENV_API_URL/rest/post/__aggregate
    --url-query select='{"_count": ["_all"]}'

The response (AggregateOutput) from this request will look like this, which contains the aggregated value:

{
  "data": {
    "_count": {
      "_all": 2
    }
  }
}

Grouping Objects

In this example, we will use a sample schema with a User object.

To group by specific fields, we can utilize the select and group_by query parameters. For a complete documentation of all the options available to you with the query parameter, take a look at the select and group_by parameter documentation below.

For an object, you can group records by one or more fields, by making a GET request to the /rest/object/__groupBy with the select and group_by parameters specified. For example, to count the number of users by country:

curl https://$ENV_API_URL/rest/post/__groupBy
    --url-query group_by='["country"]'
    --url-query select='{"country": true, "_count": ["id"]}'

The response from this request will look like this, which is the grouped and aggregated object(s):

{
  "data": [
    {
      "country": "United States",
      "_count": {
        "id": 1
      }
    },
    {
      "country": "Canada",
      "_count": {
        "id": 1
      }
    }
  ]
}

Group By with Filtering

To filter the fields before grouping, we can utilize the filter query parameter. For a complete documentation of all the options available to you with the query parameter, take a look at the filter parameter documentation below.

For example,

curl https://$ENV_API_URL/rest/post/__groupBy
    --url-query group_by='["country"]'
    --url-query select='{"country": true, "_count": ["id"]}'
    --url-query filter='{"country": {"startsWith": "C"}}'

The response from this request will look like this, which is the grouped and aggregated object:

{
  "data": [
    {
      "country": "Canada",
      "_count": {
        "id": 1
      }
    }
  ]
}

To filter the groups by aggregate functions, we can utilize the having query parameter. For a complete documentation of all the options available to you with the query parameter, take a look at the having parameter documentation below.

For example,

curl https://$ENV_API_URL/rest/post/__groupBy
    --url-query group_by='["country"]'
    --url-query select='{"country": true, "_count": ["id"]}'
    --url-query having='{"id": {"_count": {"lte": 2}}}'

The response from this request will look like this, which is the grouped and aggregated object(s):

{
  "data": [
    {
      "country": "United States",
      "_count": {
        "id": 1
      }
    },
    {
      "country": "Canada",
      "_count": {
        "id": 1
      }
    }
  ]
}

Parameters

In this section, we’ll take a look at some parameters that apply to many different operations. Availability of each parameter is dependent on the operation being performed, for example, the filter parameter is not available on Insert operations.

Once you are familiar with these parameters, we hope that utilizing individual operations on your objects comes naturally and will not require continous referral to the generated reference documentation.

filter (WhereInput)

The filter parameter allows you to add conditions to your query. This parameter can be used when retrieving objects, as well as when updating objects where it will act as the condition on which objects are updated.

Filtering with Scalars

Each scalar has it’s own set of operations that can be performed to assert a condition, for example, an integer has the following operators available to it: equals, not, in, notIn, lt, lte, gt, gte. With the exception of in and notIn, all of these operators will accept the matching scalar, in and notIn will take an array of the matching scalar.

Here is a list of all available operators: equals, not, in, notIn, lt, lte, gt, gte, contains, startsWith, endsWith, and search. The availiblity of these is dependant on the scalar type, for example, you cannot utilize the gt operator on a string. However, search is specific to strings.

A filter condition for a given object includes all of the scalars present on the object with their respective scalar filter type. To construct a filter condition utilizing only an object’s scalar properties, you can use the name of the property as the key, and then a filter object for that specific scalar type.

Here is a more comprehensive overview of these operators:

Operator

Description

Example

equals

Filter for field values equal to a specific value

To filter when age equals 21, { "age": { "equals": 21 } }

lt/lte

Filter for field values "less than / less than or equal to" a specific value

To filter when age is less than 21, { "age": { "lt": 21 } }

gt/gte

Filter for field values "greater than / greater than or equal to" a specific value

To filter when age is greater than or equal to 30, { "age": { "gte": 30 } }

not

Filters when field value does not equal a specific value

To filter records that have all ids except 100, {"id": {"not": 100}}

in/notIn

Checks if a field value exists / does not exist in a list, and filters accordingly.

To filter records having ids as either 1, 3 or 6, { "id": { "in": [1, 3, 6] } }

startsWith/endsWith

Filter when a specific value is at the start or end of a field value. Case-sensitive by default. _ wildcard can be used to match one or more characters. $ wildcard can be used to match zero or more characters.

To filter names that start with "Ron" (case-insensitive), {"name": {"startsWith": "Ron", "mode": "insensitive"} }. To filter names that start with "R", followed by one or more characters, and then "d", {"name": {"startsWith": "R_d"} }. This matches names such as "Roland" and "Rudd".

contains

Filters when a specific value is contained in a field value. Case-sensitive by default. _ wildcard can be used to match one or more characters. $ wildcard can be used to match zero or more characters.

To filter country names that contain the word "United", {"country": {"contains": "United" } }

search

Filters using full-text search capabilities. This operator is specific to strings and currently, it is not supported with MongoDB. For querying, PostgreSQL and MySQL's native full-text search capabilities are leveraged. For example, in Postgres, & can be used to perform a boolean AND on two strings. Similarly, | and ! can be used to perform boolean OR and boolean NOT, respectively. For MySQL, + and - are used as boolean AND and boolean NOT, respectively. Boolean OR in MySQL is represented using no operators; for example, Tomato Orange would check if the text contains Tomato or Orange.

In PostgreSQL, to filter records with the engine as Firefox or Chrome, use {"engine": {"search": "Firefox | Chrome"}}. In MySQL, to filter records with Firefox but not Chrome in the report, use {"report": {"search": "+Firefox -Chrome"}}.

Combining conditions

When you add conditions to a filter object, you are asserting that all of the listed conditions must evaluate to true for an object to be returned, but you are also able to further customize your query’s condition by utilizing the AND, OR, and NOT properties. All of these properties accept an array of the object’s filter type, allowing you to nest and compose different filtering conditions to construct your query.

Here is a more comprehensive overview of these operators:

Operator

Description

Example

OR

Filters for field values when one or more conditions is true

To filter names starting with either "A" or "B", {"OR": [{"name": {"startsWith": "A"}}, {"name": {"startsWith": "B"}}]}. Furthermore, to filter names starting with "A" or ending with "n", {OR: [{"name": {"startsWith": "A"}}, {"name": "endsWith": "n"}]}.

AND

Filters for field values when all conditions are true

To filter for first names starting with "A" and last names ending with "n", {"AND": [{"firstName": {"startsWith": "A"}}, {"lastName": {"endsWith": "n"}}]}. Furthermore, to filter names starting with "B" and ending with "n", we can use operators such as startsWith and endsWith in a single object itself, like {"AND": [{"name": {"startsWith": "B", "endsWith": "n"}}]}.

NOT

Filters for field values when all the conditions are false

To filter for names that are not "Alan" and not "George", {"NOT": [{"name": {"equals": "Alan"}}, {"name": {"equals": "George"}}]}. Furthermore, to filter names not starting with "C" and not ending with "n", we can use operators such as startsWith and endsWith in a single object itself, like {"NOT": [{"name": {"startsWith": "C", "endsWith": "n"}}]}.

Note on Scope of Logical operators:

  • Implicit AND: Combining conditions within one object, {"name": {"startsWith": "A", "endsWith": "n"}}, uses an implicit AND logic, meaning all conditions must be met.

  • Explicit Logical Operators: Using OR, AND, or NOT to combine multiple conditions, such as {"OR": [ {...}, {...} ]}, processes each condition in its own scope before applying the logical operator. This structure allows for complex filtering, evaluating from the inner-most conditions outwards.

Furthermore, you can combine these operators to do more powerful filtering. For example,

  • NOT and AND: To filter for names that do no start with "A" and end with "n", {"NOT": {"AND": [{"name": {"startsWith": "A"}}, {"name": {"endsWith": "n"}}]}}.

  • OR and AND: To filter for ids that are between 0 to 10 or 30 to 40, {"OR": [{"AND": [{"id": {"gte": 0}}, {"id": {"lte": 10}}]}, {"AND": [{"id": {"gte": 30}}, {"id": {"lte": 40}}]}]}.

Filtering with List

You can use the following operators to perform filtering with lists: has, hasSome, hasEvery, and isEmpty.

Additionally, you can employ the equals operator introduced earlier.

Here is a more comprehensive overview of these operators:

Operator

Description

Example

has

Filters for lists where a given value exists.

To filter when Canada is in a locations list, {"locations": {"has": "Canada"}}

hasSome

Filters for lists where at least one value from the search list exists.

To filter when Canada or United States are in a locations list, {"locations": {"hasSome": ["Canada", "United States"]}}

hasEvery

Filters for lists where all values from the search list exists.

To filter when Canada, United States, and Spain are in a locations list, {"locations": {"hasEvery": ["Canada", "United States", "Spain"]}}

isEmpty

Filters for empty lists.

To filter for empty locations list, {"locations": {"isEmpty": true}}. Similarly, to filter for non-empty lists, use {"isEmpty": false}.

Filtering with JSON

You can use the following operators to perform JSON filtering: array_starts_with, array_ends_with, array_contains, string_starts_with, string_ends_with, and string_contains.

Additionally, you can employ operators introduced earlier, such as equals, gt, gte, lt, lte, and not.

To filter a specific part of the JSON object, each of these operators requires a specified path. For example, consider a JSON object like {"Crops": {"Fruits": {"Organic": ["Orange", "Mango", "Banana"], "Non-organic": ["Apple", "Melon"]}}}. In Postgres, the path Crops -> Fruits -> Organic can be represented as ["Crops", "Fruits", "Organic"]. Similarly, for MySQL, it can be represented as '$.Crops.Fruits.Organic'.

Here is a more comprehensive overview of these operators (all the paths are specific to Postgres):

Operator

Description

Example

array_starts_with

Filters for JSON objects where a specified path has a list that begins with a specific value.

To filter when the list at ["Fruits", "Organic"] path starts with Orange, {"Crops": {"path": ["Fruits", "Organic"], "array_starts_with": "Orange"}}

array_ends_with

Filters for JSON objects where a specified path has a list that ends with a specific value.

To filter when the list at ["Fruits", "Organic"] path ends with Apple , {"Crops": {"path": ["Fruits", "Organic"], "array_starts_with": "Apple"}}

array_contains

Filters for JSON objects where a specified path has a list that contains a specific value.

To filter when the list at ["Schedule", "Heathrow"] path contains 9 , {"Misc": {"path": ["Schedule", "Heathrow"], "array_contains": 9}}

string_starts_with

Filters for JSON objects where a specified path has a string that begins with a specific value.

To filter when the string at ["Premium Customers", "NYC", "George"] path starts with Q2F0cw== , {"IDs": {"path": ["Premium Customers", "NYC", "George"], "string_starts_with": "Q2F0cw=="}}

string_ends_with

Filters for JSON objects where a specified path has a string that ends with a specific value.

To filter when the string at ["Premium Customers", "NYC", "George"] path ends with TmV1cmVsbw== , {"IDs": {"path": ["Premium Customers", "NYC", "George"], "string_ends_with": "TmV1cmVsbw=="}}

string_contains

Filters for JSON objects where a specified path has a string that contains a specific value.

To filter when the string at ["FullReport", "Browser"] path contains Firefox , {"Details": {"path": ["FullReport", "Browser"], "string_contains": "Firefox"}}

Filtering with Relations

You are able to utilize related objects in a condition too, though the manner in which you do so will depend on the type of relationship that this object has with it’s related object.

When a relationship has one of another object, you can utilize the is and isNot filters to construct conditions on a related object. For example, if we have a Post object, which has one User object, we can create a condition utilizing the User’s objects properties like so: { "user": { "is": { "name": { "equals": "George" } } } }. This filter would return all Post objects where the related User’s name is equal to George.

When a relationship has many of another object, you can utilize the some, every, and none conditions to construct conditions on a set of related objects. For example, if we have a User object that has many Posts, we can create a condition utilizing the Post’s objects properties like so:{ "posts": "some": [ { "title": { "equal": "Cats" } } ] }. This will return a list of users where at least one of their posts has the title of “Cats”.

Here is a more comprehensive overview of these operators:

Operator

Description

Example

is

Filters a record when related record "is" equal to the filter condition. Can only be used when a relationship has one of another object.

If every user has one post, to filter when name equals Alan, { "user": { "is": { "name": { "equal": "Alan" } } } }

isNot

Filters a record when related record "is not" equal to the filter condition. Can only be used when a relationship has one of another object.

If every user has one post, to filter when name does not start with "A", { "user": { "isNot": { "name": { "startsWith": "A" } } } }

some

Filters a record when related record has "some" filter conditions as true. Can only be used when a relationship has many of another object.

If a brand has many sauces, to find out brands that have "Paprika" in some of their sauces, { "sauces": { "some": [ { "ingr": { "contains": "paprika" } } ] } }

every

Filters a record when related record has "every" filter conditions as true. Can only be used when a relationship has many of another object.

If a brand has many sauces, to find out brands that have "Paprika" in all of their sauces, { "sauces": { "every": [ { "ingr": { "contains": "paprika" } } ] } }

none

Filters a record when related record has "none" of its filter conditions as true. Can only be used when a relationship has many of another object.

If a brand has many sauces, to find out brands that do not have "Paprika" in any of their sauces, { "sauces": { "none": [ { "ingr": { "contains": "paprika" } } ] } }

You can combine the sections above to construct complex queries.

select (SelectInput)

You can use the select parameter to limit the scalars that you request for a specific object, as well as to eagerly retrieve related objects for that object.

If no select parameter is specified in MongoDB, by default all scalars and all inner objects are selected automatically. In Postgres and MySQL, only scalars are selected by default.

To select specific scalars on an object, pass an object with the property key name as the key, and true as the value.

To select all scalars on an object, you can utilize the $scalars parameter in the select object.

To select all related objects, you can utilize the $related parameter in the select object. This will retrieve all direct relationships from the parent object.

To select specific related objects, you can pass in the related objects property name as a key, and true as the value.

To further specify which properties of a related object that you want to select, you can pass in an object, with the key being the related property name on the root object, and the value being another select type object, allowing you to select the relations scalars, as well as it’s own relations. There is no limit to how far you can nest Select objects to explore relationships.

You can also use select to specify the fields to be aggregated (AggregateInput). The following aggregate functions are supported: _count, _sum, _avg, _min, and _max. For example, if we have a User object that has many posts, to find the maximum number of words amongst those posts, we can use {"_max": ["words"]}.

Similarly, you can use select (GroupByInput) in conjunction with group_by option to group records by one or more fields. For a more comprehensive overview, refer to the group_by parameter documentation.

order_by (OrderByWithRelationInput)

The order_by parameter takes an array of objects, with the following definition:

{ [key: ScalarName]: "asc" | "desc" }

You can use any scalar on the object being queried as a key, along with the direction of the sort as the value in the object. We do not support ordering nested fields at this time.

For example, given a sample schema with a User object:

curl https://$ENV_API_URL/rest/user?order_by=[{"name": "desc"}]

The response from this request will look like this:

{
  "data": [
    {
      "id": 2,
      "name": "User 2",
      "country": "United States"
    },
    {
      "id": 1,
      "name": "User 1",
      "country": "Canada"
    }
  ]
}

skip & take

Pagination with Neurelo’s APIs works using the skip and take options as an implementation of offset based paginaition. You can pass an integer to each option to limit or skip the resulting objects.

For example, given a sample schema with a User object, to skip one record and take the next record:

curl https://$ENV_API_URL/rest/user?skip=1&take=1

The response from this request will look like this:

{
  "data": [
    {
      "id": 1,
      "name": "User 1",
      "country": "Canada"
    }
  ]
}

group_by

The group_by parameter takes an array of field names (Array), to specify the fields to group by with.

Note: A select parameter needs to be specified alongside group_by, such that, every selected scalar field that is not part of an aggregation (_count, _sum, _avg, _min, and _max) must be included in group_by parameter's array of field names.

For example, let us use a sample schema with a Post object,

# If the selected scalar fields in select are country and name, then group_by needs to equal ["country", "name"].
curl https://$ENV_API_URL/rest/post/__groupBy
    --url-query select='{"country": true, "name": true, "_count": ["id"]}'
    --url-query group_by='["country", "name"]'

The response from this request will look like this:

{
  "data": [
    {
      "name": "User 2",
      "country": "United States",
      "_count": {
        "id": 1
      }
    },
    {
      "name": "User 1",
      "country": "Canada",
      "_count": {
        "id": 1
      }
    }
  ]
}

having (HavingInput)

having parameter is used in conjuction with group_by parameter to filter the groups by aggregate functions. It is a special kind of filter which accepts all the filter options, together with the ability to filter aggregate functions.

To filter by aggregate function, the following definition can be used:

{ [field: ScalarName]: { [key: aggregateFunction]: filterCondition } }

Supported aggregate functions are: _count, _sum, _avg, _min, and _max. filterCondition is the same as a filter body and accepts all the filter options.

For example, let us use a sample schema with a Post object,

curl https://$ENV_API_URL/rest/post/__groupBy
    --url-query group_by='["location"]'
    --url-query select='{"location": true, "_sum": ["num_words"]}'
    --url-query having='{"num_words": {"_sum": {"gte": 1000000}}}'

The response from this request will look like this, which is the grouped and aggregated object(s):

{
  "data": [
    {
      "location": "United States",
      "_sum": {
        "id": 5179839
      }
    },
    {
      "location": "India",
      "_sum": {
        "id": 3044357
      }
    }
  ]
}

Moreover, similar to filter, you can use AND, OR, and NOT operators to do more powerful filtering. For example, to filter when the number of words is in the range of 1000 to 10000, you can use {"OR": [{"id": {"_sum": {"gte": 1000}}}, {"id": {"_sum": {"lte": 10000}}}]}.

Modeling Relationships for MongoDB with Inner Objects and References

Currently, Neurelo for MongoDB doesn't support direct manipulation of objects with relations. However, you can work with inner objects (equivalent to embedded documents, i.e., documents nested within another document) or use references to model such relationships.

To decide which data modeling approach suits your needs best, refer to our guide on how to work with embedded documents and references in MongoDB.

In summary, inner objects enhance read performance by reducing database queries and ensuring atomic updates within a single document. They are optimal for closely related data that is frequently accessed together, remains relatively static, and adheres to the 16MB document size limit. However, they might lead to data redundancy and scalability challenges as your database grows.

On the other hand, references are more suitable for large or frequently changing data. They excel in many-to-many relationships by offering the flexibility to link documents across different collections, preventing data duplication and maintaining database normalization. The trade-off, however, is the need for additional queries to get related data, which could affect your performance.

Inner Objects

Neurelo supports the management of inner objects at the schema level. To learn how to create inner objects for your schema, please refer to the Neurelo Schema Language reference.

Now, assume that will have a sample schema with a User object and an Address inner object. A user has one address. For example,

{
  "objects": {
    "user": {
      "properties": {
        "id": {
          "type": "string",
          "sourceName": "_id",
          "sourceType": "ObjectId",
          "default": {
            "function": "auto"
          },
          "identifier": true
        },
        "name": {
          "type": "string"
        },
        "address": {
          "$ref": "#/innerObjects/Address",
          "nullable": true
        }
      }
    }
  },
  "innerObjects": {
    "Address": {
      "properties": {
        "id": {
          "type": "string",
          "sourceType": "ObjectId"
        },
        "street": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "zipcode": {
          "type": "string"
        }
      }
    }
  }
}

Create

To create a single user with an address, assuming that we have this sample schema running in an environment, we can issue a POST request to /rest/user/__one with the body set to a User object. For example,

curl -X POST https://$ENV_API_URL/rest/user/__one
    --url-query select='{ "$scalars": true, "$innerObjects": true}'
    --data '{"name": "Admin User", "address": { "set": { "id": "5ca4bbc7a2dd94ee5816238d", "street": "Diagon Alley", "city": "Nowhere Land", "zipcode": "27000" } }}'

Note that, at present, our APIs do not allow the user to autogenerate inner object ids, hence we had to manually specify it in the above example. Also notice that we can use $innerObjects to select all the inner objects without the need to manually specify them.

The response from this insert request will look like this, which is the object that was inserted:

{
  "data": {
    "id": "65eb623e591811da1638040a",
    "name": "Admin User",
    "address": {
      "id": "5ca4bbc7a2dd94ee5816238d",
      "street": "Diagon Alley",
      "city": "Nowhere Land",
      "zipcode": "27000"
    }
  }
}

To create multiple users, we can issue a POST request to /rest/user with the body set to an array of User objects. For example:

curl -X POST https://$ENV_API_URL/rest/user
    --url-query select='{ "$scalars": true, "$innerObjects": true}'
    --data '[{"name": "Roger Smith", "address": { "set": { "id": "5ca4bbc7a2dd94ee5816238e", "street": "King's Court", "city": "Nashville", "zipcode": "27100" } }}, {"name": "Alan Walkman", "address": { "set": { "id": "5ca4bbc7a2dd94ee5816238f", "street": "King's Court", "city": "Nashville", "zipcode": "27100" } }}]'

The response (AffectedRowsOutput) from this insert request will look like this, which is the count of the inserted rows:

{
  "data": {
    "count": 2
  }
}

Retrieve

To retrieve a unique user with its address, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/user/{{userId}}. For example:

curl https://$ENV_API_URL/rest/user/65eb623e591811da1638040a

The response from this request will look like this, which is the retrieved object:

{
  "data": {
    "id": "65eb623e591811da1638040a",
    "name": "Admin User",
    "address": {
      "id": "5ca4bbc7a2dd94ee5816238d",
      "street": "Diagon Alley",
      "city": "Nowhere Land",
      "zipcode": "27000"
    }
  }
}

Similarly, to retrieve all users along with their addresses, assuming that we have the sample schema running in an environment, we can issue a GET request to /rest/user. For example:

curl https://$ENV_API_URL/rest/user

The response from this request will look like this, which is an array of the retrieved objects (with their inner objects):

{
  "data": [
    {
      "data": {
        "id": "65eb623e591811da1638040a",
        "name": "Admin User",
        "address": {
          "id": "5ca4bbc7a2dd94ee5816238d",
          "street": "Diagon Alley",
          "city": "Nowhere Land",
          "zipcode": "27000"
        }
      }
    }
  ]
}

Note that a select with "$innerObjects": true is not necessary for retrieving all of the corresponding inner objects.

Update

To update users with its address, assuming that we have the sample schema running in an environment, we can issue an UPDATE request to /rest/user/. For example:

curl -X PATCH 'https://$ENV_API_URL/rest/user'
--url-query filter='{ "id": { "equals": "65eb623e591811da1638040a" }}'
--data '{
  "address": {
    "update": {
      "city": "Valencia",
      "id": "5ca4bbc7a2dd94ee5816238d",
      "street": "Eixample",
      "zipcode": "960"
    }
  }
}'

The response (AffectedRowsOutput) from this update request will look like this, which is the count of the updated rows:

{
  "data": {
    "count": 1
  }
}

Delete

To delete users with its address, assuming that we have the sample schema running in an environment, we can issue an DELETE request to /rest/user/. For example:

curl -X DELETE 'https://$ENV_API_URL/rest/user'
--url-query filter='{ "address": { "is": {"id": "5ca4bbc7a2dd94ee5816238d"} }}'

The response (AffectedRowsOutput) from this delete request will look like this, which is the count of the deleted rows:

{
  "data": {
    "count": 1
  }
}

Note that this will delete both the object and its inner object. To remove the contents of just the inner object, you can use the UPDATE request with an unset:

curl -X UPDATE 'https://$ENV_API_URL/rest/user'
--url-query filter='{ "id": { "equals": "65eb623e591811da1638040a" }}'
--data '{
  "address": {
    "unset": true
  }
}'

This approach requires setting the inner object of your schema, specifically the unique ID, to have nullable set to true.

API Errors

Errors for the API calls will be presented in a list format so that multiple issues can be communicated at once. Each error object will contain an error property that contains the error message and, optionally, a details object with more information about the error.

Two types of errors can be returned as a response:

  • Validation errors - request is invalid or contains invalid data

  • Execution errors - request execution failed

For example, a sample schema and a sample request like:

Schema: 

{
  "objects": {
    "actor": {
      "properties": {
        "actor_id": {
          "type": "integer",
          "identifier": true
        },
        "first_name": {
          "type": "string"
        },
        "last_name": {
          "type": "string"
        },
        "movie_count": {
          "type": "integer"
        }
      }
    }
  }
}
Request: 

curl https://$ENV_API_URL/rest/actor
    --url-query select='{"first_name": true}'
    --url-query filter='{"movie_count": {"gt": "3"}}'
    --url-query aggregate='{"_count": ["first_name", "alias"]}'
    --url-query group_by='["first_name"]'

Will generate the following response:

Response: 

{
  "errors": [
    {
      "error": "\"3\" is not of type \"integer\"",
      "details": {
        "in": "params",
        "location": "filter/movie_count/gt"
      }
    },
    {
      "error": "\"alias\" is not one of [\"actor_id\", \"first_name\", \"last_name\", \"movie_count\"]",
      "details": {
        "in": "params",
        "location": "aggregate/_count/1"
      }
    }
  ]
}

The above example contains two errors, each with an error message and the specific location of the invalid attribute in the details section. The location parameter contains a JSON path to the attribute that failed the validation and uses “/” as a separator for each part of the path. In the case of the second error, the location contains the number “1”, indicating that the invalid attribute is contained within an array and that the element with index 1 is invalid.

Last updated