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.
Reference - Neurelo Supported Databases and Versions
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:
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 GET
calls 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:
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
)
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
The response from this insert request will look like this, which is the object that was inserted:
Creating multiple objects (CreateManyInput
)
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:
The response (AffectedRowsOutput
) from this insert request will look like this, which is the count of the inserted rows:
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
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.
The response from this insert request will look like this, which is the inserted object(s):
connect
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.
The response from this insert request will look like this, which is the inserted object(s):
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
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,
The response from this insert request will look like this, which is the inserted object(s):
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
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.
The response from this insert request will look like this, which is the inserted object(s):
Moreover, to prevent the insertion of nested records with unique fields or identifiers that already exist, we can use skipDuplicates
. For example:
The response from this insert request will look like this, which is the inserted object(s):
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:
The response from this request will look like this, which is the retrieved object:
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:
The response from this request will look like this, which is an array of the retrieved objects:
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:
The response from this request will look like this, which is an array of the retrieved objects:
Retrieving all related objects
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:
The response from this request will look like this, which is an array of the retrieved objects and their related objects:
Retrieving specific related objects
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:
The response from this request will look like this, which is an array of the retrieved objects and their specific related object:
Retrieving n-levels of related objects
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:
The response from this request will look like this, which is an array of the retrieved objects and their specific related object:
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
)
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:
The response from this request will look like this, which is the updated object:
Updating multiple objects (UpdateManyInput
)
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:
The response (AffectedRowsOutput
) from this update request will look like this, which is the count of the updated rows:
Updating scalars using special operations
increment
, decrement
, multiply
, divide
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:
The response from this request will look like this, which is the updated object:
push
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,
The response from this request will look like this, which is the updated object:
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
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.
The response from this update request will look like this, which shows the updated object(s):
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
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,
The response from this upsert request will look like this, which is the upsert object(s):
disconnect
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,
The response from this request will look like this, which is the updated object(s):
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
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,
The response from this request will look like this, which is the updated object(s):
In the above example, the Post with an ID of 2 is now deleted. Furthermore, the name of the user also gets updated.
updateMany
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.
The response from this updateMany request will look like this, which is the updated object(s):
deleteMany
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.
The response from this deleteMany request will look like this, which is the deleted object(s):
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:
The response from this request will look like this, which is the deleted object:
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:
The response from this request will look like this, which is the count of the deleted object(s):
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:
The response from this request will look like this, which is the count of the deleted object(s):
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:
The response (AggregateOutput
) from this request will look like this, which contains the aggregated value:
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:
The response (AggregateOutput
) from this request will look like this, which contains the aggregated value:
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:
The response from this request will look like this, which is the grouped and aggregated object(s):
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,
The response from this request will look like this, which is the grouped and aggregated object:
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,
The response from this request will look like this, which is the grouped and aggregated object(s):
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
)
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 implicitAND
logic, meaning all conditions must be met.Explicit Logical Operators: Using
OR
,AND
, orNOT
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
andAND
: To filter for names that do no start with "A" and end with "n",{"NOT": {"AND": [{"name": {"startsWith": "A"}}, {"name": {"endsWith": "n"}}]}}
.OR
andAND
: 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
)
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
)
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:
The response from this request will look like this:
skip
& take
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:
The response from this request will look like this:
group_by
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,
The response from this request will look like this:
having
(HavingInput
)
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,
The response from this request will look like this, which is the grouped and aggregated object(s):
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,
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,
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:
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:
The response (AffectedRowsOutput
) from this insert request will look like this, which is the count of the inserted rows:
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:
The response from this request will look like this, which is the retrieved object:
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:
The response from this request will look like this, which is an array of the retrieved objects (with their inner objects):
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:
The response (AffectedRowsOutput
) from this update request will look like this, which is the count of the updated rows:
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:
The response (AffectedRowsOutput
) from this delete request will look like this, which is the count of the deleted rows:
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
:
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:
Will generate the following response:
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