Create a Custom Data Provider with Edit Capability

By default, custom data providers created using the CDF command line tool include only query capabilities. However, you can extend a custom data provider to implement the applyEdits operation on the custom data feature service by defining custom routes and methods.

After creating a custom data provider with the CDF command line tool, follow the steps below to configure it for edit capability.

Configuring Provider Routes and Controllers

  1. Create a new file called routes.js in the providers/your_provider_name/src directory. Add the following code to the file:

    Use dark colors for code blocksCopy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = [
        {
            path: `/<provider_name>/rest/services/:host/:id/FeatureServer/:layer/applyEdits`,
            methods: ['POST'],
            handler: 'editData'
        },
        {
            path: `/<provider_name>/rest/services/:host/:id/FeatureServer/applyEdits`,
            methods: ['POST'],
            handler: 'editData'
        }
    ]

    In this file, we've created two new routes - one for layer-level editing requests and one for service-level editing requests. These routes follow the same pattern as the read-only query routes but have the applyEdits operation instead. If your custom data provider makes use of host and id path parameters, make sure these are present in the newly registered routes. applyEdits is an HTTP POST method. The value of the handler property refers the name of the function that your custom data code will execute when this route recieves a request. In this case the function name is editData, but this can be any name you choose.

  2. Create a new file called controllers.js in the providers/your_provider_name/src directory. Add
    the following code to the file:

    Use dark colors for code blocksCopy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Controller (model) {
    this.model = model
    }
    
    Controller.prototype.editData = async function (req, res) {
        const pathParams = req.params;
        const body = req.body;
        const reply = await this.model.editData(pathParams, body);
        res.status(200).json(reply);
    }
    
    module.exports = Controller

    In the file, we've registerd our handler function editData. The request path parameters and the request body are passed into editData. After editData executes its editing logic, the reply is returned.

  3. Open the file index.js in the providers/your_provider_name/src directory. Add the two lines (routes and Controller) shown in the code below to the provider object.

    Use dark colors for code blocksCopy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const packageInfo = require('../package.json')
    const csconfigInfo = require('../cdconfig.json')
    
    const provider = {
      type: csconfigInfo.type,
      name: csconfigInfo.name,
      version: packageInfo.version,
      hosts: csconfigInfo.properties.hosts,
      disableIdParam: csconfigInfo.properties.disableIdParam,
      Model: require('./model'),
      routes: require('./routes'),
      Controller: require('./controllers')
    }
    
    module.exports = provider

    At this point, the provider is configured to handle editing requests via the applyEdits route. The next step is write the custom code for performing edits on the remote data source in the editData function.

  4. Open the model.js file in the providers/your_provider_name/src directory. In the Model class, create a new editData function. At this point, the code becomes specific to your implementation. Below is a sample of a basic editData function that calls on various helper functions.

    Use dark colors for code blocksCopy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    async editData(pathParams, body) {
    
      // assign database and collection name from path parameters
      const databaseName = pathParams.host;
      const collectionName = pathParams.id;
    
      // add logic to normalize layer-level or service-level requests
      const extractedEdits = normalizeRequestedEdits(body);
    
      const database  = this.#client.db(databaseName);
      const collection = database.collection(collectionName);
    
      let applyEditsResponse = {}  // initialize the response object
    
      // call the necessary functions to handled the edits
      applyEditsResponse = await performEdits(collection, extractedEdits.edits);
    
      // check if the response should be an object(layer-level) or an array(service-level)
      if (extractedEdits.editLevel === 'service') {
        applyEditsResponse.id = extractedEdits.layer;
        return [applyEditsResponse];
    
      }
    
      return applyEditsResponse;
    
    }

Understanding the applyEdits API Specification

The editData method must be written to handle requests and return responses that conform to the applyEdits REST API specification. To ensure compatibility with ArcGIS clients, the editData method should be designed to handle both layer-level and service-level applyEdits requests.

A high-level overview of the specification is provided below. For complete details on the structure of requests and responses, refer to the REST API specification. Additionally, see this Walkthrough topic for an example of how to implement these details in a custom data provider.

Layer-level Request

The request object for a layer-level applyEdits operation includes three optional parameters: adds, updates, deletes, with at least one of these present. Each parameter is an array:

  • adds and updates: Arrays of objects where each objects contains geometry and/or attributes.
  • deletes: Either a comma-separated string of object IDs or an array of object IDs.

Example:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
adds:
  [
    {
        "geometry": {
            "x": -12246623.482500909,
            "y": 6274905.896395514,
            "spatialReference": {
                "wkid": 102100,
                "latestWkid": 3857
            }
        },
        "attributes": {
            "fireId": "C3PO",
            "fireName": "Golden Fire",
            "fireType": "Wild",
            "acres": "4682"
        }
    }
]

Layer-level Response

The response object for a layer-level applyEdits operation includes three arrays: addResults, updateResults, and deleteResults. Each array contains objects that minimally include the objectId and a success status (true or false). The arrays may be empty if no edits were requested.

Example:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
{
    "addResults": [
        {
            "objectId": 621431404,
            "success": true
        }
    ],
    "updateResults": [],
    "deleteResults": []
}

Service-level Request

The request object for a service-level applyEdits operation includes a required edits parameter, which is an array of layer objects. Each layer object contains the following arrays:

  • adds and updates: Arrays of objects where each object includes geometry and/or attributes.
  • deletes: Either an array of object IDs or a comman-separated string of object IDs.

If no edit is requested for a particular type (adds, updates, or deletes), its value will be null.

Example:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
edits:
[
    {
        "id": 0,
        "adds": null,
        "updates": [
            {
                "geometry": {
                    "spatialReference": {
                        "wkid": 102100
                    },
                    "x": -12700718.995399643,
                    "y": 6622215.403721428
                },
                "attributes": {
                    "alternateID": 3
                }
            }
        ],
        "deletes": null,
        "attachments": null,
        "assetMaps": null
    }
]

Service-level Response

The response for a service-level applyEdits operation is an array of layer objects. Each layer object includes the following arrays:

  • addResults, updateResults, and deleteResults: Each array contains objects that minimally include the objectID and a success status (true or false).

If no edits were requested, these arrays may be empty.

Example:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
[
    {
        "id": 0,
        "addResults": [],
        "updateResults": [
            {
                "success": true,
                "objectId": 3
            }
        ],
        "deleteResults": []
    }
]

Important Points

Custom data providers with edit capability require additional considerations for maintaining data integrity and accessibilty. At a minimum, this involves understanding the spatial reference and data structure of the remote data source. The metadata provided with the returned GeoJSON in the provider will also impact which attributes are editable and influence how ArcGIS clients configure editing widgets.

Spatial Reference

Your provider code should track the spatial reference of the remote data source. Once data are sent to the client, reprojections may occur based on the client’s default behavior or user preferences. Edits may be requested in a spatial reference that differs from the data source. It is highly recommended that your code compares the spatial references and, when they differ, transforms the data back to the original spatial reference. For transofrmation, use @esri/proj-codes and proj4. This process is demonstrated in this Walkthrough.

ObjectId

To be editable, features in the remote data source must include an attribute that meets the requirements for an esriFieldTypeOID. This attribute must be an integer no larger than 64 bits. For detials, refer to the CDF API Reference documentation on the idField metadata property.

Templates

Templates are essential for customizing editing widgets in ArcGIS clients. By using custom templates, you can specify which attributes are visible for editing, set default values, and apply other customizations. Refer to the CDF API Reference documentation on the templates metadata property.

fields Metadata

To make an attribute editable, set the editable property in the fields metadata. This allows fine-grained control over which feature attributes are available for editing. See the CDF API Reference documentation on the fields metadata property.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.