Skip to content

Thin layer over Shopware Store-API to enable caching using Vercel' data cache

License

Notifications You must be signed in to change notification settings

KoRoHandelsGmbH/store-api-proxy

 
 

Repository files navigation

store-api-proxy

TypeScript Badge ESLint Badge Prettier Badge Vercel Badge GitHub Actions Badge

store-api-proxy powered by Nitropack should be understood as a thin-layer on top of the Shopware' Store-API. It provides us with a easy-to-maintain way to leverage Vercel' data cache for caching (and later on transform as well as orchestrate) API responses to overcome the shortcomings of the Store API we're consuming.

Features

Installation

git checkout https://github.com/KoRoHandelsGmbH/store-api-proxy
cd store-api-proxy
npm install

How it works

The store-api-proxy is providing a thin-layer on top of the Store-API which is getting shipped with each and every sales channels created within our Shopware instance. Depending on the sales channel a different sw-access-key request header has to be provided within the incoming request.

Based on the sw-access-key the proxy is getting the right target url. If an additional sw-language-id is getting passed within the request headers it'll respect the header and forwards the request to the Store-API.

Routes we would like to be cached are provided as their own API route using a defineCachedEventHandler(). These event handler allowing a second argument for cache control. Usually we want to cache the response in Vercel' data cache for 60 minutes and provide a stale response while invalidating:

{
    maxAge: 60 * 1 * 60,
    swr: true,
    varies: ['sw-access-key', 'sw-language-id'],
}

Switching between production & development environment

When the incoming request to the store-api-proxy contains the header x-env and the content of the header is either dev or development we're switching the target url from the production servers to the integrations server.

Providing new routes

File based routes

We're using Nitro' filesystem routing for providing additional event handlers for API routes. Each and every route has to be placed into the folder server/routes/store-api. The right target url will be terminated based on the sw-access-key header of the incoming request.

When your route doesn't contain additional route parameters (e.g. for example POST /language, GET /payment-method) we simply provide the last part of the URL as the file name.

Original url: /store-api/language
File path: server/routes/store-api/language.ts

When we're dealing with routes which are containing additional route parameters (e.g. for example POST /navigation/{navigationId}/{navigationId}) we're using a catch-all routes:

Original url: /store-api/navigation
File path: server/routes/store-api/navigation/[...].ts

Providing a new event handler

After creating the necessary file using the filesystem routing we're providing a defineCachedEventHandler() including the basic cache configuration:

export default defineCachedEventHandler(
    async (event) => {},
    {
        maxAge: 60 * 1 * 60,
        swr: true,
        varies: ['sw-access-key', 'sw-language-id'],
    },
);

Next up, it's time to fill out the event handler body. For a convenient and easy-to-use way to get the necessary information from the incoming request you can use the helper method usePrepareRequest(event: H3Event). It's important here to provide the H3Event as parameter. The event contains the context, response and request.

Last but not least, we have to fire the request and providing the error reporting using createError.

const { url, requestOptions } = await usePrepareRequest(event);

try {
    const response = await $fetch(url, requestOptions);
    return response;
} catch (err) {
    throw createError(err);
}

In the end your event listener should look like this:

import { usePrepareRequest } from '~~/utils/usePrepareRequest';

export default defineCachedEventHandler(
    async (event) => {
        const { url, requestOptions } = await usePrepareRequest(event);

        try {
            const response = await $fetch(url, requestOptions);
            return response;
        } catch (err) {
            throw createError(err);
        }
    },
    {
        maxAge: 60 * 1 * 60,
        swr: true,
        varies: [
            'sw-access-key',
            'sw-language-id',
            'x-env',
            'sw-include-seo-urls',
        ],
    },
);

Transforming the response

Additionally it's possible to transform the response before sending it to the client. Let's assume we're within the route /{locale}/store-api/navigation/{navigationId}/{navigationId}.

Within your event handler body you should find a code snippet like this:

const response = await $fetch(url, requestOptions);
return response;

Before we're able to transform the response we have to define the type of the response. For that the store-api-proxy provides the entitiy schemas from Shopware Frontends API client.

First you're importing the Schemas type from the globally available module #shopware;

import type { Schemas } from '#shopware';

Next up, you map the response to the correct entity schema. In this example we're getting back an array category

const response: Schemas['Category'][] = await $fetch(url, requestOptions);
return response;

Now, you can iterate over response using Array.prototype.map() and override the properties you don't need:

const response: Schemas['Category'][] = await $fetch(
    url,
    requestOptions,
);

return response.map((item) => {
    item.description = '';

    if (item.translated) {
        item.translated.description = '';
    }

    return item;
});

Helper methods

useSalesChannel(event: H3Event)

The method useSalesChannel() is a fundamental part of the stack resolving. It's reading out the runtime config which contains the information about the available sales channels, gets the sw-access-key from the request stack and returns the right target url based on the sw-access-key.

usePrepareRequest(event: H3Event)

The method usePrepareRequest() prepares the request to be send by the proxy. It reads the body and headers of the incoming request, sanitizes the path using useSanitizedPath(), provides the correct sw-access-key using useSalesChannel() as well as the necessary headers and body for the request to be sent to the Store API.

Development

You can start the development sever after a successful npm install using:

npm run dev

This spawns up the Nitro on the port 3000. A different port can be provided using a .env file with the following content:

PORT=9210

After doing the changes you wanted to do, please commit your changes using a commit message following the conventional commit guidelines.

License

MIT

About

Thin layer over Shopware Store-API to enable caching using Vercel' data cache

Topics

Resources

License

Stars

Watchers

Forks

Languages

  • TypeScript 96.2%
  • JavaScript 3.8%