1. Scayle Resource Center
  2. Storefront
  3. Guides
  4. RPC

RPC

The data for most pages (product listings, product details, etc.) comes from the SCAYLE Engine via APIs. Developers can connect with the backend through these secure APIs, with minimal configuration. These APIs are secured through an authorization bearer token that you provide in your requests.

Since the Storefront Core is part of a Nuxt application, the Vue components are rendered on the server, but also dynamically on the client. This means simply calling the SCAYLE e-commerce engine from the client would mean we have to expose this token to the public which, of course, we don't want to do. Therefore, an API layer inside the storefront takes care of requesting and delivering the data from the SCAYLE e-commerce engine. All requests to SCAYLE APIs come from the backend of the Nuxt application rather than the browser client.

Storefront Core provides a set of APIs that can be safely called from the frontend or the backend.

The RPC methods allow us to package calls into business logic-driven methods and expose them through an automatically generated API. This way, developers do not have to spend time writing and calling REST APIs, but can quickly use that functionality inside their application. Since we are using Typescript, type support for parameters and responses are provided out of the box.

When rendering a page on the server, this behavior is even more optimized. Instead of the Vue component firing off HTTP requests to our API, the RPC methods are called directly, eliminating the whole HTTP API call roundtrip.

An example of how this works is shown below. useProduct is a composable that can be invoked during the Server-Side Rendering or on the client. It in turn depends on the getProduct RPC Method. Since getProduct is executed on the server, it has access to the API token and can call the Storefront API. Additionally, most RPC methods utilize the cache module, reducing latency and load on Storefront API.

Storefront Core API example

Using RPC Methods

useRpc

Most RPC Methods involve fetching data from the Storefront API. The useRpc composable provided by Storefront Core makes it easy to consume the data in a declarative manner.

For example, here's a simple component which uses useRpc to fetch a brand from the Storefront API. The first argument is the name of the RPC Method, the second argument is a key used to rehydrate SSR data and the final argument corresponds to the parameters of the RPC Method.

The RPC Method parameters can be a value, computed value, or getter. If it's a computed value, the data will be refreshed when it changes.

<template>
    <div>
        {{ fetching ? 'Loading...' : brand.name }}
    </div>
</template>
<script setup lang="ts">
    const route = useRoute()
    const { data: brand, fetching } = await useRpc(
        'getBrandById',
        'getBrandById',
        computed(() => ({ brandId: route.params.id })),
    )
}
</script>

This is just an example to show how useRpc is used. Generally the useBrand composable should be used for this use case.

rpcCall

RPC Methods can also be invoked directly via the rpcCall function. This is built on top of the $fetch helper from Nuxt. When invoked server-side, the RPC method will be called directly and when invoked on the client, it will trigger an API call. In either case, the function interface will remain the same.

await rpcCall('updatePassword')({
    oldPassword: 'test123',
    newPassword: 'test123!'
})

Writing an RPC method

Storefront applications can also add their own RPC Methods. These custom RPC Methods can be called with useRpc and rpcCall. They also have access to information provided on the RPCContext.

To begin, create a new file which imports the RpcContext and RpcHandler types from @scayle/storefront-nuxt.

An RPC Method must conform to the RpcHandler interface. It is a generic interface where the first generic parameter corresponds to the return type of the function and the second, optional generic parameter represents the type of the parameters that the RPC Method accepts. If no second parameter is provided, it is assumed the RPC Method accepts no parameters.

Additionally, the RpcContext is passed as an argument to every RPC Method. It provides information on the current shop and storefront configuration, along with methods that the RPC Method can invoke. It is the first argument to RPC Methods which have no parameters, and the second argument otherwise.

Below are two examples of custom RPC Methods. The first takes no parameters and returns the current campaign key. The second takes a string argument and returns its length.

import type { RpcContext, RpcHandler } from '@scayle/storefront-nuxt'

// RPC Method without params
export const getCampaignKey: RpcHandler<string | undefined> = (
  context: RpcContext,
) => {
  return context.campaignKey
}

// RPC Method with params
export const stringLength: RpcHandler<string, number> = function stringLength(
    param: string,
    context: RpcContext,
) {
    return param.length;
}

Finally, you must configure Storefront Core to load your custom RPC methods. In the module configuration, use rpcDir to specify the directory where your custom RPC methods live and use rpcMethodNames to specify the names of the methods.

let config = {
    rpcDir: './rpcMethods',
    rpcMethodNames: ['getCampaignKey', 'stringLength'], 
}

RPCContext

The context object provides information about the current application.

Some of the more important properties and methods are listed here:

  • bapiClient a reference to the Storefront API client
  • cached a utility function for caching functions
  • user the current user
  • updateUser a function to update information of the current user
  • currentShop the current active shop for the request or page
  • log a reference to the logger
  • runtimeConfiguration an object containing the properties set at runtime through the .env file

Extending types

For the typing of rpcCall and useRpc to work properly with custom RPC Methods, it's also necessary to extend the type definitions. To do this, augment the RpcMethodsStorefront interface from @scayle/storefront-nuxt.

This could be done anywhere in the project, but since the RPC methods are already imported there and it's a central location, it may be easiest to do it in the nuxt.config.ts.

import * as rpcMethods from './rpcMethods/index'

type RpcMethodsStorefrontType = typeof rpcMethods

// Add custom RPC methods to the typings
declare module '@scayle/storefront-nuxt' {
  export interface RpcMethodsStorefront extends RpcMethodsStorefrontType {}
}

Overriding RPC Methods

If a custom RPC Method has the same name as the built-in RPC Method such as getBrandById, the custom RPC Method will be called instead of the built-in RPC Method in all places. This can be used to modify or augment the behavior of a built-in method, but it's recommended to use this approach with care since it can make debugging difficult.

Extending the RPCContext

Your RPC methods may require additional information. This can be achieved by hooking into the storefront:context:created nitro runtime hook, where you can add properties to the RPC context or overwrite existing ones.

To do this, create a server plugin in which you can register a hook with nitroApp.hooks.hook('storefront:context:created', handler). The handler is a sync/async function that receives the base RPCContext as argument.

By setting properties or changing property values of the RPCContext you can change the context passed to RPC methods.

Overwriting exting properties of the RPC Context is not recomended. It might break parts of the application and therefore needs to be done very carefully.

Adding a new property to the PRC Context:

import { defineNitroPlugin } from 'nitropack/runtime/plugin'
// Augment RpxContext type
declare module '@scayle/storefront-nuxt' {
  interface AdditionalRpcContext {
    myNewProp: string
  }
}

// Set new value on rpcContext
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('storefront:context:created', (rpcContext) => {
    rpcContext.myNewProp = 'My new context value'
  })
})

Overwriting existing RPC Context values:

import { defineNitroPlugin } from 'nitropack/runtime/plugin'

// overwriting 
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('storefront:context:created', (rpcContext) => {
    rpcContext.campaignKey = 'My campaign key'
  })
})