Skip to content

Instantly share code, notes, and snippets.

@YankeeTube
Last active June 11, 2023 23:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YankeeTube/df230ac98e208768729d6842f2d27cab to your computer and use it in GitHub Desktop.
Save YankeeTube/df230ac98e208768729d6842f2d27cab to your computer and use it in GitHub Desktop.
Remix + Cloudflare KV Session

Remix On Cloudflare Worker KV Usage Example

This document started with my question on Remix.js discord.

About

This is an example of poor content in the official document, and for customization to use the prefix function, please check the contents written in the comment below.

createWorkersKVSessionStorage original code However, when I saw this code, I thought that this function had no role meaning, and there is a code for customizing it in the comment below.

We can use this to use cloudflare kv's prefix function!

Default Usage

our project would look like this is what it looks like.

.
.
└── project (root)/
    ├── app/
    │   ├── entry.server.ts
    │   ├── entry.client.ts
    │   └── root.ts
    ├── server.js
    ├── remix.config.js
    ├── remix.env.d.ts
    ├── tsconfig.json
    └── package.json

Before we start

You need to create a cloudflare kv store first.
And you can proceed with the following sequentially.

First. change package.json

{
  "private": true,
  "sideEffects": false,
  "scripts": {
    "build": "remix build",
    "dev:remix": "remix watch",
    "dev:wrangler": "cross-env NODE_ENV=development npm run wrangler",
    "dev": "npm-run-all build --parallel \"dev:*\"",
    "start": "cross-env NODE_ENV=production npm run wrangler",
    "typecheck": "tsc",
    "wrangler": "wrangler pages dev ./public --kv <KV_NAMESPACE> --persist"
  },
}

The --persist option makes development more flexible by storing KV values under the .wangler folder when used locally.

Second. change server.js

If you selected cloudflare-pages using the npx create-remix@latest command when creating the project, the server.js file should be in the root path.

import {createPagesFunctionHandler} from "@remix-run/cloudflare-pages";
import * as build from "@remix-run/dev/server-build";
import {createWorkersKVSessionStorage} from "@remix-run/cloudflare";

function sessionWrapper(kvNamespace) {
    return createWorkersKVSessionStorage({
        kv: kvNamespace,
        cookie: {
            name: "<COOKIE_NAME>",
            httpOnly: true,  // CSRF Protect + Client Access Protect
            maxAge: 3600 * 24, // expire
            sameSite: "strict", // Domain Policy
            secure: true,  //  SSL Only
            secrets: ['secretKey']
        }
    });
}

const handleRequest = createPagesFunctionHandler({
    build,
    mode: process.env.NODE_ENV,
    getLoadContext: (context) => {
      const {getSession, commitSession, destroySession} = sessionWrapper(context.env.<KV_NAMESPACE>)
      return {
        ...context.env, // You can change this part to suit your taste. I was too lazy to do it, so I hit it all.
        getSession, 
        commitSession, 
        destroySession
      }
    },
});

export function onRequest(context) {
    return handleRequest(context);
}

Third. change remix/types -> /remix.env.d.ts

import type {SessionStorage} from '@remix-run/server-runtime'

declare module "@remix-run/server-runtime" {
    export interface AppLoadContext extends SessionStorage {
        env: any
    }
}

sergiodxa disscussion

Final! app/routes/index.ts

import type {LoaderArgs} from "@remix-run/cloudflare";

export const loader = async ({request, context}: LoaderArgs) => {
    const {getSession, commitSession} = context;
    const session = await getSession(request?.headers?.get("Cookie"));
    session.set('test', '1')
    console.log(session.has('test')) // true
    return json({host: uri.origin}, {
        headers: {
            "Set-Cookie": await commitSession(session),
        },
    })
}
@YankeeTube
Copy link
Author

YankeeTube commented Mar 4, 2023

Custom Cloudflare KV Session

You must be able to include prefixes in order to use listening later using cloudflare kv.

The method of creating PR in remix.js is good, but there was a problem that it took a long time for them to merge.
Also, createWorkersKVSessionStorage This function is just a wrapper. that didn't do anything special.

So I've also applied a little bit of modification to the code that they do, and it works very well.

Change server.js

import {createPagesFunctionHandler} from "@remix-run/cloudflare-pages";
import * as build from "@remix-run/dev/server-build";
import {createSessionStorage} from "@remix-run/cloudflare";

export function SessionWrapper(kv) {
    return createSessionStorage({
        cookie: {
            name: "<COOKIE_NAME>",
            httpOnly: true,
            maxAge: 3600 * 24,
            sameSite: "strict",
            secure: true,
            secrets: ['secretKey']
        },
        async createData(data, expires) {
            while (true) {
                let prefix = data?.prefix ? `${data.prefix}_` : ''
                let id = prefix + crypto.randomUUID().replace(/-/gi,'').slice(0, 16)

                if (await kv.get(id, "json")) {
                    continue;
                }

                await kv.put(id, JSON.stringify(data), {
                    expiration: expires
                        ? Math.round(expires.getTime() / 1000)
                        : undefined,
                });

                return id;
            }
        },
        async readData(id) {
            let session = await kv.get(id);

            if (!session) {
                return null;
            }

            return JSON.parse(session);
        },
        async updateData(id, data, expires) {
            await kv.put(id, JSON.stringify(data), {
                expiration: expires ? Math.round(expires.getTime() / 1000) : undefined,
            });
        },
        async deleteData(id) {
            await kv.delete(id);
        },
    })
}

const handleRequest = createPagesFunctionHandler({
    build,
    mode: process.env.NODE_ENV,
    getLoadContext: (context) => {
        const {getSession, commitSession, destroySession} = SessionWrapper(context.env.<KV_NAMESPACE>)
        return {
            ...context.env,
            getSession,
            commitSession,
            destroySession,
        }
    },
});

export function onRequest(context) {
    return handleRequest(context);
}

Why is the method of generating random numbers different from the original code?

Just.
The pseudo random number generation of the original code is unnecessarily long.
I like shorter and simpler code because I use the same browser built-in module called 'crypto' anyway.
Of course, I've spent my time discussing this publicly, and you can customize it again and make it your own way.
Good luck!

Why is it good to create customization?

At first, I tried to generate PR, but there were a total of five other services that refer to the same function, including cloudflare kv.
That's all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment