How to use Durable Objects from Remix on Cloudflare Workers
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