Data Providers

The main purpose of Olobase Admin is to manage remote resources from a specific API. When it needs to communicate with your backend API for any standardized CRUD operation, it calls the adapted method in your provider that will be responsible for fetching or updating the resource data.

- packages
    - admin
        - providers
            - data
                jsonServer.js
// Fetching books
let { data, total } = await provider.getList("books", { page: 1, perPage: 10 });
console.log(data);

// Fetching one book
let { data } = await provider.getOne("books", { id: 1 });
console.log(book)
// Create new book
await provider.create("books", { data: { title: "My title" } })
// Update title book
await provider.update("books", { id: book.id, data: { title: "New title" } });
// Delete book
await provider.delete("books", { id: book.id });

All fetch methods of a data provider are standardized to ensure compatibility of Olobase Admin with any API server. This is the adapter model that allows for all kinds of different providers for each type of backend of any exchange protocol, whether it's REST, GraphQL, or even SOAP.

Data Providers

To give Olobase Admin the ability to retrieve remote source data, a specific data provider needs to be injected into the constructor method as explained in the next section. This data provider defaults to JsonServerProvider.

API Contract

As always with any adapter model approach, all data providers must comply with a specific contract to allow communication with Olobase Admin. The next object represents the minimum contract that must be implemented:

const dataProvider = {
  getList:    (resource, params) => Promise,
  getOne:     (resource, params) => Promise,
  getMany:    (resource, params) => Promise,
  create:     (resource, params) => Promise,
  update:     (resource, params) => Promise,
  updateMany: (resource, params) => Promise,
  delete:     (resource, params) => Promise,
  deleteMany: (resource, params) => Promise,
  copy:       (resource, params) => Promise,
  copyMany:   (resource, params) => Promise,
}

Supported API Operation Methods

getList Data Iterator component for displaying a list of resources within a data table or any custom list layout components. It also supports searching, filtering, sorting, and optional relationship fetching.
getOne It is used to show details of the resource, especially the Show action or the data table show actions.
getMany It is used only for the AutoCompleter component to fetch all available selections by IDs on initial load, whether in editing page content or query context filtering.
create Used by VaForm to create new resources.
update Used by VaForm to update the existing resource.
delete Simple delete action called when interacting with Delete Button.

Setting Up a Data Provider

Data providers are very simple to use. They can take a simple API URL or a custom HTTP client of your choice as the first constructor argument. If you need to share some headers throughout the admin application, use the full object; It's especially useful for authentication-related topics.

src/plugins/admin.js

import {
  jsonServerDataProvider,
  jwtAuthProvider,
} from "olobase-admin/src/providers";
import { en, tr } from "olobase-admin/src/locales";
import config from "@/_config";

let admin = new OlobaseAdmin(import.meta.env);
/**
 * Install admin plugin
 */
export default {
  install: (app, http, resources) => {
    // console.error(app.config.globalProperties)
    admin.setOptions({
      app,
      router,
      resources,
      store,
      i18n,
      dashboard: "dashboard",
      downloadUrl: "/files/findOneById/",
      readFileUrl: "/files/readOneById/",
      title: "Demo",
      routes,
      locales: { en, tr },
      dataProvider: jsonServerDataProvider(http),
      authProvider: jwtAuthProvider(http),
      http,
      canAction: null,
      // canAction: ({ resource, action, can }) => {
      //   if (can(["admin"])) {
      //     return true;
      //   }
      //   // any other custom actions on given resource and action...
      // },
      options: config,
    });
    admin.init();
    OlobaseAdmin.install(app); // install layouts & components
    app.provide("i18n", i18n);
    app.provide("router", router);
    app.provide("admin", admin); // make injectable
    app.component("PageNotFound", PageNotFound);
  },
};

Writing Your Own Data Provider

Writing your own data provider is pretty simple. Note the method call signatures by copying the JsonServerProvider. As mentioned before each provider method takes 2 arguments:

  • resource: represents the string name of the relevant resource and must be the resource API URL base for each call.
  • params : a specific object tailored to each type of API call.

Method Call Signatures

The following table lists which parameters can be sent in the second parameter for each provider method.

Method Description Parameter Format Response
getList Lists data { pagination: { page: Number , perPage: Number }, sort: [{ by: String, desc: Boolean }], filter: Object }, include: String[], fields: { [resource]: String[] } } { data: Resource[], total: Number }
getOne Returns a resource (data) by id { id: Any } { data: Resource }
getMany Returns multiple data based on id { ids: Array, include: String[], fields: { [resource]: String[] } } { data: Resource[] }
create Creates new data { data: Object } { data: Resource }
update Updates a default resource (data) { id: Any, data: Object } { data: Resource }
updateMany Updates multiple data { ids: Array, data: Object } empty
delete Deletes current data { id: Any } empty
deleteMany Deletes multiple data { ids: Array } empty
copy Copies the selected data { id: Any } { data: Resource }
copyMany Copies multiple selected data { ids: Array } { data: Resource }

Here are some valid examples of calls to Olobase Admin in each resource repository module:

dataProvider.getList("books", {
  pagination: { page: 1, perPage: 15 },
  sort: [{ by: "publication_date", desc: true }, { by: "title", desc: false }],
  filter: { author: "Cassandra" },
  include: ["media", "reviews"],
  fields: { books: ["isbn", "title"], reviews: ["status", "author"] }
});
dataProvider.getOne("books", { id: 1 });
dataProvider.getMany("books", { ids: [1, 2, 3] });
dataProvider.create("books", { data: { title: "Lorem ipsum" } });
dataProvider.update("books", { id: 1, data: { title: "New title" } });
dataProvider.updateMany("books", { ids: [1, 2, 3], data: { commentable: true } });
dataProvider.delete("books", { id: 1 });
dataProvider.deleteMany("books", { ids: [1, 2, 3] });

Handling Errors

In case of any server side errors, i.e. when the response status is outside the 2xx range, you simply return a rejection promise with a specific Object containing at least a descriptive error message as well as the HTTP status code. This status is passed to the authentication provider to allow you to perform a specific authentication action based on a specific status code.

In case of multiple errors, we return to error, and in case of an empty response or server error from the server, we return to the general error error.

try {
  let response = await this.admin.http('post', url, data);
} catch (e) {
    if (
      && e.response 
      && e.response.status === 400 
      && e.response["data"] 
      && e.response["data"]["data"]
      && e.response["data"]["data"]["error"]
      ) {
      this.admin.message("error", e.response.data.data.error);
    }
}

Expected error object format:

Key Type Description
error string Error message to be displayed in Snackbar
status number Response status code sent by the server
errors array Error objects sent by the server for client-side validation support

Store

You can use all data provider methods for each resource in your custom CRUD pages or any authenticated custom page directly from the Vuex store. You have 2 different methods, one with the mapActions Vuex helper and the other with the global $store instance where you can use dispatch.

The following code shows both ways of getting data from your providers under one example:

<template>
  <v-row>
    <v-col v-for="item in data" :key="item.id">
      {{ item.name }}
    </v-col>
  </v-row>
</template>

<script>
import { mapActions } from "vuex";

export default {
  data() {
    return {
      data: [],
    }
  },
  async mounted() {
    /**
     * Use the global vuex store instance.
     * You need to provide the name of the resource followed by the provider method you want to call.
     * Each provider methods needs a `params` argument which is the same object described above.
     */
    this.data = await this.$store.dispatch("publishers/getList", {
      pagination: {
        page: 1,
        perPage: 5,
      },
    });

    /**
     * Use the registered global method which use global `api` store module.
     * Then you need to provide a object argument of this format : `{ resource, params }`
     */
    this.data = await this.getList({
      resource: "publishers",
      params: {
        pagination: {
          page: 1,
          perPage: 5,
        },
      },
    });
  },
  methods: {
    ...mapActions({
      getList: "api/getList",
    }),
  },
};
</script>