How do you properly access Cloudflare resources from within a SvelteKit app?

In a SvelteKit application, how are the various server.js files related to Cloudflare Workers?

If my SvelteKit application wants to use something like R2 or D1, do you put that code inside the appropriate JS file within your SvelteKit application, or does do you put it into an external JS file that is a separate Cloudflare Worker and then call that Worker from your SvelteKit JS?

Example: You have a SvelteKit application that wants to read some values from R2, D1 or KV, do you:

  1. Use ENV bindings in /src/routes/my-page/+page.js?

  2. Use ENV bindings in /src/routes/my-page/+page.server.js?

  3. Make a separate Cloudflare Worker file with its own endpoint (outside SvelteKit) and:

    3.a: Call the Worker in /src/routes/my-page/+page.js?

    3.b: Call the Worker in /src/routes/my-page/+page.server.js?

The SvelteKit documentation for Cloudflare Pages seems to suggest that we should use Server Endpoints for our “Functions”:

SvelteKit Docs - Cloudflare Pages:
https://kit.svelte.dev/docs/adapter-cloudflare

If so, does this mean that we should build our SvelteKit app using API endpoints as much as possible and then from within those JS files we use ENV bindings to access the various Cloudflare resources?

Bonus Round:

How does any of this change if you want to use DurableObjects? The DurableObjects Chat App example is great, but where does that code “go” in a SvelteKit app?

2 Likes

I’d have managed to do this on Cloudflare, but not on local development.

In app.d.ts, you need:

declare global {
	namespace App {
		interface Platform {
			env: {
				KV: KVNamespace;
				DB: D1Database;
				BUCKET: R2Bucket;
			};
		}
	}
}

In +page.server.js, the load function exposes platform

export const load = (async ({ platform }) => {

You can then access service on platform.env.DB, platform.env.KV and platform.env.BUCKET.

This will work on Cloudflare after you have manually linked each service to your worker.

Locally, platform is undefined, so this does not work. I have tried using Wrangler, but do not understand how to get it to work. I’d love to know how to get it to work locally.

1 Like

So for local development we need the /functions directory but for deployment we don’t? Or can we skip the /functions directory?

Functions contained in the /functions directory at the project’s root will not be included in the deployment, which is compiled to a single _worker.js file.

I’m having so much trouble wrapping my head around this.

If I read
https://developers.cloudflare.com/pages/platform/functions/

I would assume that I would not need to use
https://kit.svelte.dev/docs/adapter-cloudflare-workers

Unless you have a specific reason to use this adapter, we recommend using adapter-cloudflare instead.

If I compare the two cloudflare adapters side-by-side I can see that the adapter-cloudflare (https://kit.svelte.dev/docs/adapter-cloudflare#comparisons) should be enough since Functions is a part of Cloudflare Pages.

But it seems we need to have the /functions directory at the root level of the project. I’m wondering whether that is always true or whether there is an exception for Sveltekit.

As long as your directory follows the proper structure, Pages will identify and deploy your functions to our network with your site.

Maybe the “proper structure” for Sveltekit is different than it is for other frameworks and we don’t need the /functions directory.

Another complication is the documentation at https://kit.svelte.dev/docs/adapter-cloudflare#bindings

“SvelteKit’s built-in $env module should be preferred for environment variables… To make these types available to your app, reference them in your src/app.d.ts:”

Are the env variables in scr/app.d.ts connected to the $env module? In other words, how to use the preferred module, $env instead of or along with the src/app.d.ts file?

I just posted part of this thread on Discord: https://discord.com/channels/595317990191398933/1125970612234506291/1125970612234506291

For local development I added an NPM script to package.json

"wrangle": "wrangler pages dev .svelte-kit/cloudflare -k KV_1 -k KV_2 --compatibility-date 2023-05-18 --port 5173 -e dev"
  • @sveltejs/adapter-cloudflare builds to the .svelte-kit/cloudflare folder
  • In a terminal I run yarn wrangle. If not using yarn I assume you need npm run wrangle.
  • For live reloads I first run yarn build --watch and when it has finished building I run yarn wrangle. I find it isn’t so optimal because wrangler keeps serving stale files and I often have to kill and restart it.

AFAICT you can only access your CF worker bindings via the platform object injected into load functions and form action functions. From there you can obviously pass it as a parameter to other methods… It would be handy if platform was available for import into modules that need it just like env - maybe it is possible but I haven’t found it yet!

1 Like

@cheftech, @a_cf_enthusiast Maybe this can help.

I have the following setup so I can run the environment both in dev (using vite) and prod.

in hooks.server.ts

import type { Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { devSteps } from './hooks/dev-hooks';

export const handle: Handle = sequence(
	...devSteps,
);

in ./hooks/dev-hooks.ts

import { dev } from '$app/environment';
import type { Handle } from '@sveltejs/kit';

export const devStep_setupKVPlatform: Handle = async ({ event, resolve }) => {
	if (dev) {
		const { fallBackPlatformToMiniFlareInDev } = await import('$lib/clients/miniflare');
		event.platform = await fallBackPlatformToMiniFlareInDev(event.platform);
	}
	return resolve(event);
};

export const devSteps = [devStep_setupKVPlatform];

finally in $lib/clients/miniflare:

import { Miniflare, Log, LogLevel } from 'miniflare';
import { dev } from '$app/environment';

const MINIFLARE_KV_NAMESPACE = 'your_kv';
const MINIFLARE_KV_PERSIST_PATH = './kv-data'; //kv dev config refer to Miniflare documentation

const miniflareScript = `
    addEventListener("fetch", (event) => {
        event.waitUntil(Promise.resolve(event.request.url));
        event.respondWith(new Response(event.request.headers.get("X-Message")));
    });
    addEventListener("scheduled", (event) => {
        event.waitUntil(Promise.resolve(event.scheduledTime));
    });
`;

const miniflareConfig = {
	log: new Log(LogLevel.INFO),
	kvPersist: MINIFLARE_KV_PERSIST_PATH,
	kvNamespaces: [MINIFLARE_KV_NAMESPACE],
	globalAsyncIO: true,
	globalTimers: true,
	globalRandom: true,
	script: miniflareScript
};

export const fallBackPlatformToMiniFlareInDev = async (_platform: App.Platform) => {
	if (!dev) return _platform;
	if (_platform) return _platform;

	const mf = new Miniflare(miniflareConfig);
	await mf.dispatchFetch('https://host.tld'); //simulate a fetch request to start the Miniflare worker
	const env = await mf.getBindings(); //get the environment bindings from the script

	const platform: App.Platform = { env: <{ ASIMED_KV: any }>env }; //create a platform with the bindings

	return platform;
};

I am only using the KV store but I presume everything would work fine. You would need to go through the Miniflare documentation to setup everything locally. The benefit of doing this is that you avoid having to use wranger and hot module reloading works fine.

Then just follow the docs at: https://kit.svelte.dev/docs/adapter-cloudflare#bindings

I wonder about this too as the platform gets set on every request.

1 Like

I just realized I had answered something similar in this GitHub issue: https://github.com/sveltejs/kit/issues/2966#issuecomment-1251898659

1 Like

@ricardo11 , this works great for KV
have to managed to do this with a local D1 db maybe?

1 Like

Hi, I have not used D1 locally as I don’t have a great use case to use D1.

If anyone comes accross this, I finally got a lot of good results with. Using KV and D1 myself.
https://github.com/james-elicx/cf-bindings-proxy
It’s changed my dev experience a lot. Wrangler should have that built in.

2 Likes