🌤️

How to use Durable Objects from Remix on Cloudflare Workers

2022/05/20に公開

Overview

I implemented the remix application as a module worker for cloudflare workers to use Durable Objects from remix.
This article shows an example of implementation of a simple counter with Durable Objects.

Code and execution

Check out remix-durable-object-counter and execute the following command.
remix will start locally.

npm install
npm run dev

This code has been tested on ubuntu 20.04.

When you open http://localhost:8787 in the browser, you will see the following screen.

Brief description of implementation

Implementing a remix application using Durable Objects

If a module worker is implemented and configured to use Durable Objects, an object that manipulates Durable Objects be passed as an argument of load function and action function at run time.

The implementation for incrementing the counter is as follows.

app/routes/index.tsx

import { json } from "@remix-run/cloudflare";
import type {
  LoaderFunction,
  DataFunctionArgs,
  ActionFunction,
} from "@remix-run/cloudflare";
import { Form, useActionData, useLoaderData } from "@remix-run/react";

export const loader: LoaderFunction = async ({
  request,
  context,
}: DataFunctionArgs) => {
  const url = new URL(request.url);

  const counter = context.COUNTER as DurableObjectNamespace;
  const id = counter.idFromName("A");
  const obj = counter.get(id);
  const resp = await obj.fetch(`${url.origin}/current`);
  const count = await resp.text();

  return json(count);
};

export const action: ActionFunction = async ({
  request,
  context,
}: DataFunctionArgs) => {
  const url = new URL(request.url);

  const counter = context.COUNTER as DurableObjectNamespace;
  const id = counter.idFromName("A");
  const obj = counter.get(id);
  const resp = await obj.fetch(`${url.origin}/increment`);
  const count = await resp.text();

  return count;
};

export default function Index() {
  const count = useLoaderData();
  const actionData = useActionData();

  return (
    <div>
      <h1>Welcome to Remix on cloudflare workers!</h1>
      <div>count: {count}</div>
      {actionData && <div>action result: {actionData}</div>}

      <Form method="post">
        <button type="submit">increment</button>
      </Form>
    </div>
  );
}

Implementation of module worker

To use durable objects, implement the remix application as a module worker as follows.

worker/index.ts

import { createFetch } from "remix-cloudflare-workers-fetch";
import * as build from "../build";
//@ts-ignore
import assetJson from "__STATIC_CONTENT_MANIFEST";
import type { ServerBuild } from "remix-cloudflare-workers-fetch";
import { Counter } from "./counter";

const fetch = createFetch({
  build: build as unknown as ServerBuild,
  assetJson,
  mode: "production",
  options: {
    cacheControl: {
      bypassCache: true,
    },
  },
});

export default {
  fetch,
};

export { Counter };

The implementation of the Counter class as Durable Objects itself is almost directly from [Counter implementation examples in official documentation](https://developers.cloudflare.com/workers /learning/using-durable-objects/#example---counter).
No modification is required to use it from remix.

The remix application imports as follows.

import * as build from "../build";

This import is for distributing remix static files using workers sites.

//@ts-ignore
import assetJson from "__STATIC_CONTENT_MANIFEST";

For more information about workers sites, please refer to the official documentation.

Remix build

Remove the server property from remix.config.js in order to build the remix application and module worker separately.

remix.config.js

/**
 * @type {import('@remix-run/dev').AppConfig}
 */
module.exports = {
  serverBuildTarget: "cloudflare-workers",
  // server: "./server.js",
  devServerBroadcastDelay: 1000,
  ignoredRouteFiles: ["**/.*"],
  // appDirectory: "app",
  // assetsBuildDirectory: "public/build",
  // serverBuildPath: "build/index.js",
  // publicPath: "/build/",
};

With this setup, the following command will output the server-side module to /build.

npx remix build

Modules created by remix build are imported in worker/index.ts.

Build module worker

In the sample code, worker is implemented in the worker directory.
The worker is built by esbuild.
The build configuration is as follows.

build-worker.ts

import { build } from "esbuild";

build({
  entryPoints: ["./worker/index.ts"],
  bundle: true,
  sourcemap: true,
  format: "esm",
  outfile: "dist/index.mjs",
  minify: true,
  external: ["__STATIC_CONTENT_MANIFEST"],
}).catch(() => process.exit(1));

build with the following command.

npx ts-node build-worker.ts

If the build is successful, dist/index.mjs will be created.

Execution of worker

Set up wrangler.toml as follows

name = "remix-durable-object-counter"
main = "dist/index.mjs"
workers_dev = true
compatibility_date = "2022-05-18"

[site]
bucket = "./public"

[durable_objects]
bindings = [{ name = "COUNTER", class_name = "Counter" }]

Execute the following command.

npx wrangler dev

That is all.

Discussion