docs
  1. SCAYLE Resource Center
  2. Developer Guide
  3. Basic Setup
  4. RPC methods

RPC methods

Overview

Storefront Core is built on top of a set of so-called RPC Methods. They are JavaScript function that follow a certain interface and are registered as RPC methods. These 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 is provided out of the box.

Universal rendering

Nuxt offers a variety of rendering modes with the most typical being Universal Rendering. With Universal rendering, the Vue components are rendered both on the server and the client.

However, RPC methods will only be executed server-side. If a call to an RPC method is made while rendering server-side, the RPC method's function will be called directly. When an RPC method is called from client-side rendering, an HTTP request will be made to execute the RPC method on the server and respond with the result.

This is all handled automatically and the API to call RPC methods does not differ. In Storefront Core we make all API calls to SCAYLE within RPC methods. This means that the calls are only made server-side and we do not expose API tokens to the client and certain implementation details remain private.

Rendering a page on the server

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

Write an RPC method

Storefront applications can also add their own RPC Methods which can then be called in the application. These RPCs also have the same access to the RPCContext which provides information about the session, the shop and sensitive tokens.

Writing an custom RPC should be done for logic which calls another service, either a SCAYLE system or a third party provider.
Additionally it can also be used to ensure sensitive code/data doesn't get bundled into the client.

To get started, simply create a new file in the rpcMethods directory of your project:

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

// RPC Method without params
export const getShopId = async function getShopId(context: RpcContext) {
  return context.shopId
} satisfies RpcHandler<number>

Here we create a new RPC called getShopId which will return the shop identifier provided through the context.

The RPC also receives no input and it satisfies the RPC contract.

It is important that we export our RPC function here, and we also must export this function from the rpcMethods/index.ts file. To do so, add the following line to the file:

// This will export all RPC functions from the shop.ts file
export * from './shop'

Receive Arguments for your RPC

Some of the RPCs may also require to receive parameters. To pass them, add the expected parameters as the first argument in your RPC method.

The RpcContext is still provided into the second argument of the RPC.

Additionally we need to add this type to the satisfies RpcHandler<Input, Output> code for the type definitions.

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

// RPC Method with parameters
export const subscribeToNewsletter = async function subscribeToNewsletter(
  params: { email: string },
  _: RpcContext,
) {
  // TODO: Subscribe the user to your newsletter
  return params.email
} satisfies RpcHandler<{ email: string }, string>

We now have an RPC method called subscribeToNewsletter that receives an email and subscribes it to your newsletter. Currently, it returns the email as the result of the RPC.

Override RPC Methods

Since Storefront Core also includes its own set of RPCs, there is a possibility of name conflicts, which could result in accidentally overriding an existing RPC method.

In this case you will receive a warning in your console indicating which RPC method was overridden, as it can lead to unexpected side effects and cause the application to behave improperly.

However, in certain situations it may be necessary to override RPC methods from Storefront Core. This should be done with extreme caution and only when absolutely necessary.

To not spam the console if this is a case, you can add the overridden RPC method to the storefront.rpcMethodOverrides configuration in your nuxt.config.ts.
This will silence this warning for the specific RPC method.

If an RPC method is overridden, we cannot guarantee that the Storefront Core will work as expected. It becomes your responsibility to test all relevant cases.

Calling RPC Methods

Now that we have written our custom RPC Methods, we need some way of calling them from our composables or components.

Depending on your use case and requirements, you have two options for executing your RPC:

1. useRpc: This provides a declarative approach to calling the RPC within a page, component, or composable.

2. useRpcCall: This should be used when the RPC is triggered in response to an event or user interaction, rather than during the initial setup.

Both useRpc and useRpcCall are composables and must follow composable usage rules.

It may also help to familiarize yourself with the Data fetching guide from Nuxt. useRpc is based on useAsyncData and should be used in a similar manner, while rpcCall is more like $fetch.

useRpc

useRpc offers a declarative way to do data fetching in your application. It manages the fetch state, automatically calls the RPC method and returns you the result or an error.

A lot of the composables exported by Storefront Core also rely on useRpc and and serve as light wrappers around the RPCs we provide.

Let's say we want to call our getShopId RPC from our previous example:

<template>
    <div>
        {{ fetching ? 'Loading...' : shopId }}
    </div>
</template>
<script setup lang="ts">
import { useRpc } from '#storefront/composables'
    
const { data: shopId, fetching } = useRpc('getShopId', 'current-shop-id')
</script>

The first argument to useRpc is our RPC Method name which is just the name under which the RPC is exported.

The second argument is a unique key which Nuxt uses to prevent refetching the same data and to repopulate the SSR data. Learn more about this here.
We generally recommend making the key unique to the location and passing this data explicitly around instead of relying on the assumption that the same key is used somewhere and will not trigger a refetch of the data.

The third argument are the parameters for an RPC if it has any, in our example, we don't have to pass any parameters.
The argument can receive a reactive function or a computed value, which will automatically retrigger a refetch if any of the data in the parameters changes.

<template>
  <div>
    {{ fetching ? 'Loading...' : content.title }}
  </div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRpc } from '#storefront/composables'

const props = defineProps<{ id: number }>()

// Let's imagine we have a custom RPC called `getCmsContent` which takes an ID parameter
// We pass this as a computed value to automatically refetch the data in case the ID changes
const { data: content, fetching } = useRpc(
  'getCmsContent',
  `cms-content-${props.id}`,
  computed(() => ({ id: props.id })),
)
</script>

Handling of shared state

By default, useRpc utilizes a shared cache, causing all instances called with the same key to share the same data. You can disable this behaviour globally by setting the disableDefaultGetCachedDataOverride option in the public runtime config.

export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      storefront: {
        disableDefaultGetCachedDataOverride: true,
      }
    }
  }
})

However you can also disable this on a single call to useRpc:

<template>
  <div>
    {{ fetching ? 'Loading...' : content.title }}
  </div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRpc } from '#storefront/composables'

const props = defineProps<{ id: number }>()

const { data: content, fetching } = useRpc(
  'getCmsContent',
  `cms-content-${props.id}`,
  computed(() => ({ id: props.id })),
  { getCachedData: undefined }
)
</script>

If it is disabled globally, it can be also enabled for certain useRpc call:

<template>
  <div>
    {{ fetching ? 'Loading...' : content.title }}
  </div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRpc } from '#storefront/composables'

const props = defineProps<{ id: number }>()

const { data: content, fetching } = useRpc(
  'getCmsContent',
  `cms-content-${props.id}`,
  computed(() => ({ id: props.id })),
  {
    getCachedData: (key, nuxtApp) => {
      const hydrationData = nuxtApp.isHydrating
        ? nuxtApp.payload.data[key]
        : nuxtApp.static.data[key]
      return hydrationData ?? nuxtApp._asyncData[key]?.data.value
    },
  },
)
</script>

useRpcCall

useRpcCall is designed to be used when you need to call an RPC in response to events or user interactions, such as clicking a button to add a product to the basket or subscribing to a newsletter.

We can use again our example subscribeToNewsletter RPC which is triggered when the user submits the newsletter form.

<script setup lang="ts">
import { useRpcCall } from '#storefront/composables'
import { ref } from 'vue';

const subscribeToNewsletter = useRpcCall('subscribeToNewsletter')
const email = ref('[email protected]')

const onSubmit = async () => {
  const res = await subscribeToNewsletter({ email: email.value })

  console.log(res)
}
</script>

useRpcCall takes the RPC Method name and returns a callback. When this callback is invoked, it receives the parameters for the RPC and makes the request. It returns a promise that resolves with the response from the RPC.

It then returns a promise which will resolve to the response provided by the RPC.

RPC Context

The RPC context object provides information about the current application, application secrets and helpers for RPCs.

Some of the most important properties and methods include:

Property NameDescription
sapiClientA reference to the Storefront API client initialized to the shop of the request.
campaignKeyThe key for the currently active campaign. By default, we will choose any active campaign at the time of the request.
cachedA utility function for caching functions.
userThe current user.
wishlistKeyThe wishlist identifier for the current session.
basketKeyThe basket identifier for the current session.
sessionIdThe unique session id of the current user.
shopIdThe shop id of the current request.
logA reference to the logger.
runtimeConfigurationAn object containing the private properties set at runtime through the environment.

Extend RPCContext

Your RPC methods may require additional information which can also be added to the RPC Context to make it available in all of your RPCs.

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 its argument.

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

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

Adding a new property

To add a new property to the PRC Context, use the following snippet:

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

// Augment RpcContext type to make sure our application is also aware of the property
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'
  })
})

Overwrite existing values

It is possible to overwrite or change existing values in the RPC Context. However, this should be carefully and we generally do not recommend making such changes.

If it is absolutely necessary, follow this approach:

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

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