- Model definition
- Modify an existing model
- Create a composite model
- Field name mappings
- Field input validation
- Model input validation
- Customizing generated model APIs
- Programmatic CRUD interface
- Restricting CRUD endpoints
- Predefined or custom endpoints
This guides covers the basics for creating Models. Models are data stored in either server memory or a backend service, such as ArrowDB or a MySQL database, using an Arrow Connector. Models are accessed like standard REST objects using predefined endpoints that Arrow automatically generates by default. You can either create a model by defining your own schema, use an existing model defined by a connector, modify an existing model by either extending or reducing it, or create a composite model by joining two or more models together.
To programmatically create Models, see the Arrow.Model API reference.
Place all Model files in the
- Loads the
- Calls the module's
createModel('name', schema)method (or another
Modelmethod), passing in the name of the model as the first parameter and an object defining the model schema as the second parameter
- Exports the defined endpoint using the
Set the following keys in the object passed to the
createModel() method to define the model:
|fields||true||An object that represents the model’s schema define as key-value pairs. The key is the name of the field and the value is the |
|connector||true||Connector to which the model is bound (string). Each model can only have one connector. Connectors are responsible for reading and writing data from/to their data source.|
|documented||false||Since Release 5.0.0. Determines whether to generate API documentation (true) or not (false). Default is true.|
|metadata||false||Used to provide connector specific configuration (e.g., mapping the model to a specific database table for the MySQL connector or defining the join properties).|
|autogen||false||Used to determine whether to generate API endpoints directly from the model. The default value is |
|actions||false||An array of data operations supported by the model. The valid values are: ‘create’, ‘read’, ‘update’, and ‘delete’. By default, all are supported by the model.|
|plural||false||A string used as the property name when your API endpoint returns an array. By default, the plural value is the plural of the model name. For example, if your model is named car, the default plural would be cars. Note: this value can be set on an API or a model.|
|singular||false||A string used as the property name when your API endpoint returns a single record. By default, the singular value is the name of the model. Note: this value can be set on an API or a model.|
|before||false||One or more blocks to be executed before the request. Blocks are referenced by their |
|after||false||One or more blocks to be executed after the request. Blocks are referenced by their |
fields property (mentioned above) supports a number of sub-properties as well. The table below outlines these properties.
|required||false||Specifies whether the field is required. The default value is |
|validator||false||A function or regular expression that validates the value of the field. The function is passed the data to validate and should return either |
|name||false||Used if the model field name is different than the field name in the connector’s model or the underlying data source for the field name. For example, if my model field is |
|default||false||The default value for the field.|
|description||false||The description of the field (used for API documentation).|
|maxlength||false||The max length of the field (specified as an integer)|
|get||false||A function used to set the value of a property that will be sent to the client. This property is useful if you want to define a custom field where the value is derived.|
|set||false||A function used to set the value of a property that will be sent to the connector.|
|custom||false||This property should be specified and set to |
|model||false||Model name of the field property. This is either the logical name of a custom model or a connector model name in the form connector/model_name (e.g., appc.mysql/employee)|
Model schema example
The example below creates the
car model with the specified schema. The car models will be stored in ArrowDB as CustomObjects. Since the
autogen property was not set to
false, Arrow automatically generates the pre-define endpoints for the client to access the car models using the
Modify an existing model
Besides creating a fully defined model, you can modify an existing model either by reducing or extending it.
Reduce a model
A reduced model is an existing model where you only use specific fields from it. To create a reduced model, follow the same procedure when creating a regular model except invoke the module's
Model.reduce() method instead of the
createModel() method. Pass the model you want to reduce as the first parameter, the name of the new model as the second parameter, and the new model schema as the last parameter.
The Model file below extracts three fields from the
employee table of the
appc.mysql connector, indicated by the
appc.mysql/employee parameter, and renames the fields for the
baseEmp model, for example,
email_address in the MySQL employee table maps to
Extend a model
An extended model is an existing model where you modify the fields or add more fields. To create an extended model, follow the same procedure when creating a regular model except invoke the module's
Model.extend() method instead of the
createModel() method. Pass the model you want to extend as the first parameter, the name of the new model as the second parameter, and the new model schema as the last parameter.
The Model below extends the employee model by adding the
headquarters field to it.
Create a composite model
Composite models allow you to create a single model that is composed of one or more models based on the same or different connectors. Composite models can be joined together via a common set of properties, such as primary keys or foreign keys, or they can have no properties in common at all. The power of composite models is that you can represent multiple data sources and entities as a single API endpoint, which is ideal for many mobile use cases.
To create a composite model, follow the same procedure when creating a regular model except the
connector property must be set to
appc.composite, each field in the definition object must specify the
model property to indicate which model the field originates from, and the
metadata property must define the join operation to combine the models or leave it undefined to perform no join operations.
The following terms are used to refer to models:
- Model definition: The composite model which is being created
- Main model: The main source of data for the composite model. This is the left table in SQL terminology. It is implicitly defined.
- Secondary model: Any model other than the main model. This will be the right table in SQL terminology.
The composite connector can either perform a left join or inner join:
- left join: all records from the main model are returned regardless if it found a match in the secondary models
- inner join: only records that match both models are returned
The composite connector can also perform either a one-to-one join or one-to-many join:
- one-to-one: only one record from the secondary model matches a record in the main model
- one-to-many: multiple records from the secondary model can match a record in the main model
There are different ways that a one-to-one join and a one-to-many join can work when merging (mapping) data from a secondary model into the main model:
- Merge as object: This is a one-to-one relation where the whole secondary model record will be mapped to a field in the main model.
- Merge as array: This is a one-to-many relation where multiple records from the secondary model will be mapped to an array field in the model definition.
- Merge as field: This is a field which comes directly from a joined model. The field in the model definition must have a 'name' property which refers to the field being joined from the secondary model. By default, this is a one-to-one relation where the field will contain a single match. In the Join Object Definition, 'multiple' may be set to true for all of the matches to be mapped to the field. Since this returns multiple values, the field type must be Array if 'multiple' is set to true.
The composite connector can be used to perform "reduce" functionality on a single model. This only requires the main model and does not require any joins. The API Builder Console offers its functionality using this method. Without any joins, a one-to-one merge as field is the only functionality available.
To define the join operation, set the
metadata property to the
left_join key or
inner_join key, either of which takes an array of objects defining the join. Each object in the
inner_join property defines the model to join (
model property), key to join (
join_properties property) and, optionally, if the join is a
Join object definition
|model||String||Name of the model. For left joins, this is the secondary model you want to join with the main model.|
|join_properties||Object||Collection of key-value pairs that determine the keys in each model to perform the join operation. The key is the property of the model defined in this object and the value is the property to join in another model (or the main model for left joins).|
|multiple||Boolean||Determines whether the match is one-to-one (false) or one-to-many (true) . Default is false. If true, the field being joined on must be of type Array and have a |
Left join example
The example below combines the
managers models to create the employee_manager model. The models are joined based on a match between the managers model's
employee_id and the employee model's auto-generated
Inner join example
The example below performs an inner join on the
employee_habit models. Both the
Field name mappings
You often want the ability to use a field property name in your model that is different from its name in an existing model. The following example shows how you can use the
name sub-property of a field to map a model property name to a specific property name of an existing custom model or connector generated model. For example, the employee model has a property called first_name, but the new model wants that property to be called fname. The Arrow framework ensures this mapping occurs bidirectionally.
Field input validation
You might need to perform validation on a field when creating or updating a record. Each property in your model definition can specify a validation function using the
validator field property. This function is called before sending data to your model’s connector. The
validator function is passed the value of the property. If the value is valid, the function should return
undefined. If inot valid, the function should return a message indicating why the validation failed. The following is an example of a validator function on a field.
You might need to perform validation on a whole model. Specify in your model definition a validation function using the
validator model property. This function is called before sending data to your model’s connector. The
validator function is passed the instance of the model. If the value is valid, the function should return
undefined. If not valid, the function should return a message indicating why the validation failed or throw an exception. The following is an example of a validator function on a model.
Customizing generated model APIs
You can customize the generated APIs for your models. For example, by default the create API only returns a status 201 with a header Location pointing to the newly created instance. No body content is returned. If you want to directly receive the newly created instance in the body of the request, add the
includeResponseBody: true metadata to your model.
All models inherit the CRUD interfaces supported by their underlying connector. As a result, you can programmatically call these interfaces. The main use case for using a model’s CRUD interface is when you want more control of an API's functionality. You can place logic in your API endpoint’s
action function to handle custom business functionality and control execution of data access.
The following are the main interfaces most connectors support.
The following model has example uses.
deleteAll function on a model to delete all of its records.
The following is an example of creating a record and then updating and deleting it. It’s not necessarily a practical example, but demonstrates how to use some additional interfaces available on a model.
The following is a simple example of performing a query against a model.
Restricting CRUD endpoints
By default, models support the basic CRUD methods (CREATE, READ, UPDATE, and DELETE). You can limit the methods supported by a model by using the
In this example the model only allows
create (POST) or
read (GET). DELETE and PUT are not allowed and would fail.
The valid values for the
action property are:
Predefined or custom endpoints
By default, Arrow generates the following API endpoints for models:
GET /api/<model_name>: Return all objects (the first 1000 records).
GET /api/<model_name>/query: Return all objects that satisfy a query.
GET /api/<model_name>/:id: Return a specific object by id
GET /api/<model_name>/distinct: Find distinct objects
GET /api/<model_name>/count: Count objects
PUT /api/<model_name>/:id: Update a specific user by id
PUT /api/<model_name>/findAndModify: Find and modify an object
POST /api/<model_name>: Create a new object
POST /api/<model_name>/upsert: Create or update an object
DELETE /api/<model_name>/:id: Delete a specific object by id
DELETE /api/<model_name>: Delete all objects
To disable Arrow from generating these endpoints, set the Model's
autogen property to
false when defining the model. You will need to create Arrow API objects to access the model.
The following model disabled generating pre-defined endpoints. An API endpoint needs to be defined to access the model data as shown below.
The example below implements the
GET /api/<employee>/:id endpoint that would normally be generated by Arrow.