DoneJS StealJS jQuery++ FuncUnit DocumentJS
5.33.3
6.0.0 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
    • getting started
      • CRUD Guide
      • Setting Up CanJS
      • Technology Overview
    • topics
      • HTML
      • Routing
      • Service Layer
        • Introduction
        • Configuring Requests
        • Managing Sessions
        • Customizing Connections
      • Debugging
      • Forms
      • Testing
      • Logic
      • Server-Side Rendering
    • app guides
      • Chat Guide
      • TodoMVC Guide
      • TodoMVC with StealJS
    • beginner recipes
      • Canvas Clock
      • Credit Card
      • File Navigator
      • Signup and Login
      • Video Player
    • intermediate recipes
      • CTA Bus Map
      • Multiple Modals
      • Text Editor
      • Tinder Carousel
    • advanced recipes
      • Credit Card
      • File Navigator
      • Playlist Editor
      • Search, List, Details
    • upgrade
      • Migrating to CanJS 3
      • Migrating to CanJS 4
      • Migrating to CanJS 5
      • Using Codemods
    • other
      • Reading the API Docs
  • API Docs
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

Configuring Requests

  • Edit on GitHub

URL, query, and response formats for making requests.

Overview

This document describes how to configure can-connect for loading model data from a remote server. Every model in your application will have its own connection, and each connection will be able to Create, Read, Update, and Delete (CRUD) model data from the server. There are two sections of this document:

  1. Part 1 - Making requests - configuring your connection for outgoing requests
  2. Part 2 - Handling the response - parsing and formatting response data for use within your app

Part 1 - Making requests

When configuring a connection, the data-url behavior is responsible for making AJAX requests, and this behavior expects the URLs and data to be formatted a certain way. If your services do not line up with what is expected, we will describe how to configure things to meet your needs.

Note: all examples will be using the can-rest-model as it serves as a good starting point for learning how to use can-connect. For those familiar with can.Model, the can-rest-model is essentially the next generation.

Understanding the DataInterface

Each of the CRUD operations maps to one of the functions descibed by the DataInterface. The DataInterface is a lower level interface used for loading raw data from a remote data source. This is not the same as the InstanceInterface which deals with typed data. Understanding the raw DataInterface is crucial for making the customizations described below.

IMPORTANT: Whenever loading data within your application, you almost always want to use the InstanceInterface. The lower level DataInterface should be used for customizing how raw data is loaded .

  • getListData - loads a list of records (with optional filtering, sorting, and pagination)
  • getData - loads an individual record by {id}
  • createData - creates a new record
  • updateData - updates an existing record by {id}
  • destroyData - deletes a record by {id}

Configuring the URL

When configuring a connection with the data-url behavior, a single URL can be used to describe all CRUD endpoints.

const connection = restModel({
  url: "/api/todos/{id}"
});

The above is equivalent to the following long-hand configuration:

const connection = restModel({
  url: {
    getData:     "GET /api/todos/{id}",
    getListData: "GET /api/todos",
    createData:  "POST /api/todos",
    updateData:  "PUT /api/todos/{id}",
    destroyData: "DELETE /api/todos/{id}",
  }
});

Here is a working example you can play with in your browser:

import { restModel } from "can";
import { Todo, todoFixture } from "//unpkg.com/can-demo-models@5/index.mjs";

// create mock data
todoFixture(5);

// define the connection
const connection = restModel({
  url: "/api/todos/{id}",
  Map: Todo
});

// load data
connection.getList({}).then(todos => {
  console.log(todos);
});

Note: For more information, read about the data-url behavior, the url configuration, and the DataInterface.

Customizing the request method and URL

Some applications will not follow the conventions expected by the data-url behavior. For example an application might only support GET and POST request methods or might use a unique URL structure. You can configure individual CRUD endpoints by defining the method and URL for each endpoint:

const connection = restModel({
  url: {
    getListData: 'GET /api/todos/all',
    getData:     'GET /api/todo?uuid={id}',
    createData:  'POST /api/todo/create',
    updateData:  'POST /api/todo/update?uuid={id}',
    destroyData: 'POST /api/todo/delete?uuid={id}',
  }
});

Implementing the DataInterface yourself

Consider a situation where an application loads all incomplete TODOs from a special URL like /api/todos/incomplete. In such cases, you can write a custom getListData function which performs the actual ajax request. You can implement any of the DataInterface methods in a similar way:

import ajax from 'can-ajax';

const connection = restModel({
  url: {
    getListData(query) {
      if(query.filter.complete === false) {
        // Load all incomplete TODOs from a separate URL
        return ajax({ url: '/api/todos/incomplete', data: query, /* ... */ });
      }

      // Load all other TODOs from the primary URL
      return ajax({ url: '/api/todos', data: query, /* ... */ });
    }
  }
});

// Loads incomplete TODOs from '/api/todos/incomplete'
connection.getList({ complete: false });

// Loads all TODOs from '/api/todos'
connection.getList({});

Customizing the AJAX data transport

By default, can-ajax is used to make all data requests using XMLHttpRequest (XHR) and is based on the jQuery.ajax interface. Any jQuery compatible transport can serve as a drop-in replacement for can-ajax. Here is how you would use jQuery's ajax transport for all requests:

import $ from "jquery";

const connection = restModel({
  url: "/api/todos/{id}",
  ajax: $.ajax
});

Using another library for AJAX requests

You can create a thin wrapper to translate the ajaxOptions expected by can-ajax into options for another library. For example, here is how you could use axios with can-connect:

import axios from 'axios';

function axiosTransport({ url, type, data, dataType }) {
    const hasBody = /POST|PUT|PATCH/i.test(type);

    // Must return a Promise
    return axios({
        url,
        method: type,
        params: hasBody ? {} : data,
        data: hasBody ? data : {},
        responseType: dataType || 'json',
    }).then(res => {
        // Must resolve to the parsed data object
       return (typeof res === 'string') ? JSON.parse(res) : res;
    });
}

const connection = restModel({
    url: "/api/todos",
    ajax: axiosTransport
});

Note: Any data transport can be used so long as it can conform to the can-ajax interface, which is based on the jQuery.ajax interface.

Creating your own data-url behavior

In really advanced situations, you can create your own custom data-url behavior for making AJAX requests. You will need to implement the required DataInterface methods described above. Every method must return a Promise which resolves to the expected data:

connect.behavior("data/url", function( baseConnection ) {
  return {
    getListData(query) {
      return ajax({
        type: "GET",
        url: this.url,
        data: query
      });
    },
    getData(query) { return ajax( /* ... */ ) },
    createData(data) { return ajax( /* ... */ ) },
    updateData(data) { return ajax( /* ... */ ) },
    destroyData(data) { return ajax( /* ... */ ) }
  };
});

Loading list data

Loading list data is unique because data can be filtered, sorted, and paginated using can-query-logic. This is where the real power of can-connect becomes available as it enables advanced behaviors such as the constructor store, real-time updates, caching, and other goodness. We recommend reading the following documents to become familiar with how to query logic works:

  • The the comprehensive guide on can-query-logic
  • Learn about the query structure
  • Check out the available comparison operators used for filtering data

Part 2 - Parsing, formatting, and managing response data

Once raw data is loaded from a server, that data needs to be instantiated into typed data for use within your applicaiton. The constructor behavior is responsible for instantiating data returned from your services, and this behavior expects data to be formatted a certain way. If your services do not return data in the expected format, we will describe how to configure things to meet your needs.

Understanding how can-connect manages Model instances

Many of the behaviors available with can-connect are designed to work with instances of Model data for your application. A single constructor/store is used to keep references to instance data and prevent multiple copies of an instance from being used by the application at once.

For example, if you have 3 different components which display information about the currently logged in User, you can rest easy knowing that all 3 components will receive the same exact instance of that user. If the user gets updated by one component, all other components will receive those updates thanks to the real-time behavior.

Customizing how response data his handled

There are two types of response formats expected by the constructor behavior:

  • Instance Data: - The result of most CRUD operations. The full response body is treated as the instance data.

    {
      id: 111,
      name: 'Justin',
      email: ...
    }
    
  • List Data: The result of a a call to getList. An array of instance data must be on a data property in the response body.

    {
      count: 3,
      data: [
        { id: 111, name: 'Justin', /* ... */},
        { id: 222, name: 'Brian', /* ... */},
        { id: 333, name: 'Paula', /* ... */}
      ]
    }
    

If your services return data in a format which is not expected, the following configurations can be used to customize how data is read and parsed from a response:

  • parseInstanceProp - a custom property for looking up instance data
  • parseListProp - a custom property for looking up list data
  • parseInstanceData - a function for formatting response data
  • parseListData - a function for formatting response data

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 5.33.3.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news