Can service workers cache POST requests?

31,847

Solution 1

You can't cache POST requests using the Cache API. See https://w3c.github.io/ServiceWorker/#cache-put (point 4).

There's a related discussion in the spec repository: https://github.com/slightlyoff/ServiceWorker/issues/693

An interesting solution is the one presented in the ServiceWorker Cookbook: https://serviceworke.rs/request-deferrer.html Basically, the solution serializes requests to IndexedDB.

Solution 2

I've used the following solution in a recent project with a GraphQL API: I cached all responses from API routes in an IndexedDB object store using a serialized representation of the Request as cache key. Then I used the cache as a fallback if the network was unavailable:

// ServiceWorker.js
self.addEventListener('fetch', function(event) {
    // We will cache all POST requests to matching URLs
    if(event.request.method === "POST" || event.request.url.href.match(/*...*/)){
        event.respondWith(
            // First try to fetch the request from the server
        fetch(event.request.clone())
            // If it works, put the response into IndexedDB
            .then(function(response) {
                // Compute a unique key for the POST request
                var key = getPostId(request);
                // Create a cache entry
                var entry = {
                    key: key,
                    response: serializeResponse(response),
                    timestamp: Date.now()
                };

                /* ... save entry to IndexedDB ... */

                // Return the (fresh) response
                return response;
            })
            .catch(function() {
                // If it does not work, return the cached response. If the cache does not
                // contain a response for our request, it will give us a 503-response
                var key = getPostId(request);
                var cachedResponse = /* query IndexedDB using the key */;
                return response;
            })
        );
    }
})

function getPostId(request) {
    /* ... compute a unique key for the request incl. it's body: e.g. serialize it to a string */
}

Here is the full code for my specific solution using Dexie.js as IndexedDB-wrapper. Feel free to use it!

Solution 3

If you are talking about form data, then you could intercept the fetch event and read the form data in a similar way as below and then save the data in indexedDB.

//service-worker.js
self.addEventListener('fetch', function(event) {
      if(event.request.method === "POST"){
         var newObj = {};

               event.request.formData().then(formData => {

                for(var pair of formData.entries()) {
                  var key = pair[0];
                  var value =  pair[1];
                  newObj[key] = value;
                }

              }).then( ...save object in indexedDB... )
      }
})

Solution 4

Another approach to provide a full offline experience can be obtained by using Cloud Firestore offline persistence.

POST / PUT requests are executed on the local cached database and then automatically synchronised to the server as soon as the user restores its internet connectivity (note though that there is a limit of 500 offline requests).

Another aspect to be taken into account by following this solution is that if multiple users have offline changes that get concurrently synchronised, there is no warranty that the changes will be executed in the right chronological order on the server as Firestore uses a first come first served logic.

Solution 5

According to https://w3c.github.io/ServiceWorker/#cache-put (point 4).

        if(request.method !== "GET") {
            return Promise.reject('no-match')
        }
Share:
31,847
Aniket
Author by

Aniket

Updated on August 06, 2020

Comments

  • Aniket
    Aniket almost 4 years

    I tried to cache a POST request in a service worker on fetch event.

    I used cache.put(event.request, response), but the returned promise was rejected with TypeError: Invalid request method POST..

    When I tried to hit the same POST API, caches.match(event.request) was giving me undefined.

    But when I did the same for GET methods, it worked: caches.match(event.request) for a GET request was giving me a response.

    Can service workers cache POST requests? In case they can't, what approach can we use to make apps truly offline?