iTranslated by AI
Integrating the Prisma Driver Adapter for Cloudflare D1 into Remix
A few days ago, Prisma released version 5.11, which works with Edge Functions.
With this, all major ORMs and Query Builders for JavaScript runtimes now support Edge Functions.
I have written some code to integrate this into Remix, which supports Cloudflare Pages as an edge environment, so I will write a brief guide on how to set it up.
Conclusion (As of 2024/3/19)
- The build size of Prisma alone occupies nearly 1MB, so only the paid version of Cloudflare Workers will work.
- Prisma migrate is not supported for reflecting changes to Cloudflare D1, so you need to execute the DDL output by Prisma against Cloudflare D1 manually.
Finished Code
Excluding the installation commands, there are only 3 commits to get it set up and create working sample code, so it should be a quick read.
Remix Setup
Remix has supported Vite since version 2.7, so we will create the project by running the Vite version installation command.
npx create-remix@latest --template remix-run/remix/templates/vite-cloudflare
corepack enable yarn
corepack use yarn
yarn install
In my case, I use yarn instead of npm.
Prisma Setup
Once the Remix setup is complete, we'll install Prisma, the main topic. This part will be the same for Prisma + D1 even if you are not using Remix.
yarn add @prisma/client @prisma/adapter-d1
yarn add -D prisma @cloudflare/workers-types wrangler
After that, create the schema file by initializing it.
yarn run prisma init --datasource-provider sqlite
Since Cloudflare D1 is SQLite-based, we will match Prisma to it. Write a sample model in the created schema file.
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model User {
id Int @id @default(autoincrement())
email String
name String?
}
The previewFeatures in this schema file is currently required.
Next, run the command to generate the DDL based on the schema file you created.
yarn run prisma migrate dev --name add_user_model
This will create a SQLite database locally and the tables within it.
Unlike Drizzle's migration, Prisma does not support migration directly to Cloudflare D1 itself. Therefore, a bit of extra effort is required from here.
Migrating to Cloudflare D1
Creating a Cloudflare D1 Database
First, since we haven't created a Cloudflare D1 database yet, let's create one.
wrangler d1 create RemixPrismaD1 # Replace RemixPrismaD1 with any name you like
Running this command creates a D1 database on your Cloudflare account. Even for local use, the following configuration is required:
d1_databases = [
{ binding = "RemixPrismaD1", database_name = "RemixPrismaD1", database_id = "test-db-id" }
]
Cloudflare D1 Migration
Migrating to Cloudflare D1 is quite simple: you just need to store SQL files in a directory named migrations/. Therefore, we just need to move the DDL generated by Prisma earlier into the migrations/ directory.
In my case, I created a simple script using zx as follows:
#!/usr/bin/env zx
await $`mkdir -p ./migrations`
const packages = await glob(['prisma/migrations/*/migration.sql'])
for (let i = 0; i < packages.length; i++) {
const migrationName = packages[i]
.replace('prisma/migrations/', '')
.split('/')[0]
if (!fs.existsSync(`migrations/${migrationName}.sql`)) {
await $`cp ${packages[i]} migrations/${migrationName}.sql`
}
}
await $`yarn run wrangler d1 migrations apply RemixPrismaD1 --local`
Prisma creates a file at prisma/migrations/<migration name>/migration.sql. This script simply moves that file to migrations/<migration name>.sql and finally runs the wrangler d1 migrations command.
As a side note, I'm using --local with wrangler d1 migrations to run the migration against the local D1 SQLite database, but recent versions of wrangler run locally by default even without --local. To migrate to your Cloudflare account—the actual D1 database—you use the --remote option. (I believe this is intended to prevent accidental execution without the --local flag.)
Referencing Cloudflare D1 from Remix with Prisma
Creating the Connection Definition from Prisma to Cloudflare D1
The following code shows how to generate a client using the Prisma adapter for D1.
import { PrismaClient } from '@prisma/client'
import { PrismaD1 } from '@prisma/adapter-d1'
export const connection = async (db: D1Database) => {
const adapter = new PrismaD1(db)
return new PrismaClient({ adapter })
}
It's very straightforward, simply specifying @prisma/adapter-d1 as the adapter.
Building CRUD Operations Using Prisma
Finally, we will build CRUD functionality for Cloudflare D1 using Prisma in Remix. First, generate the type definition file to use Cloudflare D1.
yarn run wrangler types # Defined in the typegen script, but written here explicitly for explanation purposes
Wrangler has a command to generate type definitions from the wrangler.toml file. By running it, you can generate a worker-configuration.d.ts file like the one below:
interface Env {
RemixPrismaD1: D1Database;
}
Remix reads this, providing the types for using D1. In practice, you might create a file named load-context.ts and store it in the context as follows:
+ import { type AppLoadContext } from '@remix-run/cloudflare'
import { type PlatformProxy } from "wrangler";
+ import { connection } from './app/database/client'
type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;
declare module "@remix-run/cloudflare" {
interface AppLoadContext {
cloudflare: Cloudflare;
+ db: Awaited<ReturnType<typeof connection>>
}
}
+ type GetLoadContext = (args: {
+ request: Request
+ context: {
+ cloudflare: Cloudflare
+ } // load context _before_ augmentation
+ }) => Promise<AppLoadContext>
+ export const getLoadContext: GetLoadContext = async ({ context }) => {
+ return {
+ ...context,
+ db: await connection(
+ context.cloudflare.env.RemixPrismaD1,
+ ),
+ }
+ }
You then need to configure this in the Vite config and Cloudflare Pages Functions.
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
+ import { getLoadContext } from "./load-context";
export default defineConfig({
- plugins: [remixCloudflareDevProxy(), remix(), tsconfigPaths()],
+ plugins: [remixCloudflareDevProxy({ getLoadContext }), remix(), tsconfigPaths()],
});
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
// eslint-disable-next-line import/no-unresolved
import * as build from "../build/server";
+ import { getLoadContext } from "../load-context";
- export const onRequest = createPagesFunctionHandler({ build });
+ export const onRequest = createPagesFunctionHandler({ build, getLoadContext });
By doing this, you can use Prisma via context within Remix's loader or action.
import { type LoaderFunctionArgs, json } from "@remix-run/cloudflare";
export const loader = async ({ context }: LoaderFunctionArgs) => {
const users = await context.db.user.findMany()
return json({ users: users })
}
import type { ActionFunctionArgs } from "react-router";
export const action = async ({ request, context }: ActionFunctionArgs) => {
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
await context.db.user.create({
data: {
name: name as string,
email: email as string,
},
});
return null
}
After that, you just need to display the data from the loader.
import { Form, useLoaderData } from "@remix-run/react";
import { loader, action } from "./handlers";
export default function UserPage() {
const { users } = useLoaderData<typeof loader>()
return (
<div>
<Form method="post">
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" />
<br />
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" required />
<button type="submit">Add</button>
</Form>
<h1>Users</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
)
}
export { loader, action };
This completes a simple sample. If you really want to run Prisma on the Edge, please give it a try.
I actually tried deploying it to Cloudflare Pages as well, and it did work. However, as I'll mention later, the built file size exceeds 1MB, so it only works on the paid version of Cloudflare Workers.
Conclusion
Prisma has finally supported Edge Functions, but unfortunately, there are still hurdles to overcome to use it effectively. That hurdle is the size. Prisma has supported Edge Functions by converting its Engine part to Wasm, but Prisma's size exceeds 1MB.
Measured results
Remix + Prisma bundle size
$ yarn run wrangler pages functions build --outfile=test/_worker.bundle --minify
✨ Compiled Worker successfully
$ gzip -c9 ./test/_worker.bundle > ./test/bundle.gz
$ du -sh test/*
2.8M test/_worker.bundle
1.0M test/bundle.gz
Size before gzip
$ yarn run wrangler pages functions build --minify --outdir ./test2
✨ Compiled Worker successfully
$ du -sh test2/*
1.9M test2/5343c5664d9b411cd10438666dc2381e8c450cda-query_engine_bg.wasm
848K test2/index.js
In short, Prisma alone becomes nearly 2MB (around 770KB after gzip) in file size. Since the limit for the free version of Cloudflare Workers is 1MB, it can be deployed, but it will not be reflected correctly.

As for other factors, they look like this:


Cloudflare Pages Functions (Cloudflare Workers) are more affected by cold start times as the size increases. This size of Prisma is quite challenging, even if the paid version has no 1MB limit.
mizchi has suggested a way to bypass the size limit using Service Bindings, so please take a look if you are interested.
Currently, it's still in Early Access, so if you want to use Prisma, keep an eye on future developments.
While writing this article, I noticed that Drizzle has become quite easy to use, so I plan to redo the comparison between Kysely, Drizzle, and Prisma and write another article about it soon.
Discussion
Prismaを導入したかったので参考にさせていただいています!
最近Cloudflareを触り始めたためそもそも何か認識が違うかもしれませんが、1点質問させてください🙇🏻♂️
このような記述があるのですが公式ドキュメントを見ると以下のような記述があります。
Cloudflare Workersはコールドスタートがほぼないこと(0ns cold start)が特徴だと思っていたんですが、1MBを超えた場合はさすがに影響が出てくる、みたいな意味合いで書いてくださったのでしょうか?
はい、さすがに0ではありません。
正確な数字を測ってないので、今度どこかのタイミングで測ろうと思いますが昔はもっとサイズ制限がきつい時期がありその時に中の人からサイズによってコールドスタートに影響が出てくると伺っております。
ありがとうございます〜。疑問解消できました!🙏🏻