iTranslated by AI
Deploying Next.js and Prisma to Cloudflare: Handling 3 Million Monthly DB Queries for Free
Introduction
When you host Next.js on Cloudflare, it inevitably runs in the Edge Runtime environment. However, unlike the Node.js Runtime, Prisma cannot be used as is in the Edge Runtime.
The first solution that comes to mind is Prisma Accelerate. Prisma Accelerate is an official service equipped with connection pooling and global caching, making Prisma usable in the Edge Runtime.
However, the free plan has a limit of 60,000 queries per month, which leaves some anxiety for production use.
Therefore, in this article, I will introduce how to build your own Prisma Accelerate on Cloudflare Workers and develop a production-ready service for free. With this method, you can handle up to 3 million queries per month even on the free plan.
For the implementation, I referred to this article and the sample code from the developer of the package mentioned below.
prisma-accelerate-local
To build your own Prisma Accelerate, you use a package called prisma-accelerate-local.
Originally intended for local development environments, you can self-host Prisma Accelerate by running this on Cloudflare Workers.
Preparing Cloudflare Workers
First, prepare the development environment for Cloudflare Workers. You can easily build it by cloning the following repository and following the instructions in README.md.
Also, please refer to the sample repository by the developer of prisma-accelerate-local.
The following content provides the steps to build the above repository from scratch.
Creating a Cloudflare Workers project
First, create a project. Use Cloudflare Wrangler to create it from the CLI.
npx wrangler init prisma-accelerate-pg-workers
You can answer y or yes to all questions.
When the option to choose the type of application appears, select "Hello World" Worker (you can select it with the space bar).
Answering yes to the final question will automatically deploy it to Cloudflare Workers.
What type of application do you want to create?
● "Hello World" Worker
○ "Hello World" Worker (Python)
○ "Hello World" Durable Object
○ Website or web app
○ Example router & proxy Worker
○ Scheduled Worker (Cron Trigger)
○ Queue consumer & producer Worker
○ API starter (OpenAPI compliant)
○ Worker built from a template hosted in a git repository
Installing Packages
Install the prisma-accelerate-local package and other Prisma-related packages.
Note that this guide assumes the use of PostgreSQL.
npm install @prisma/client prisma-accelerate-local @prisma/adapter-pg @prisma/adapter-pg-worker @prisma/pg-worker
Setting environment variables in .dev.vars
Create a .dev.vars file in the root directory and set the environment variables as follows.
Replace xxx with an arbitrary random string. This value will later be used as a seed to generate the API_KEY.
PRISMA_ACCELERATE_SECRET=xxx
Run the following command to reflect the changes in the deployment destination.
You will be prompted for input, so enter the same value as above.
npx wrangler secret put PRISMA_ACCELERATE_SECRET
Creating Cloudflare KV
Go to the Cloudflare dashboard.
Select Workers & Pages from the left menu, then select KV under it.
You will be redirected to a screen like this, so click Create Namespace on the right.

Set an easily identifiable name for Namespace Name. Here, we'll use prisma-accelerate-pg-workers. After entering it, click Add.
Take note of the ID of the created KV.

Configuring wrangler.toml
Next, edit the wrangler.toml file.
In wrangler.toml, you configure settings related to the Cloudflare Workers deployment.
Uncomment the necessary lines from the commented-out settings and edit them.
Set the ID of the KV you just created in the id field of kv_namespaces.
#:schema node_modules/wrangler/config-schema.json
name = "prisma-accelerate-pg-workers"
main = "src/index.ts"
compatibility_date = "2024-05-29"
compatibility_flags = ["nodejs_compat"]
+ minify = true
# Automatically place your workloads in an optimal location to minimize latency.
# If you are running back-end logic in a Worker, running it closer to your back-end infrastructure
# rather than the end user may result in better performance.
# Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+ [placement]
+ mode = "smart"
...
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces
+ [[kv_namespaces]]
+ binding = "KV"
+ id = "xxxxxx"
...
Type Definitions
First, use Wrangler to automatically generate types for reading environment variables.
npm run cf-typegen
Types will be generated in worker-configuration.d.ts.
interface Env {
KV: KVNamespace;
PRISMA_ACCELERATE_SECRET: string;
}
Next, create a types directory and define types for Prisma and Wasm.
declare module '@prisma/client/runtime/wasm.js' {
export * from '@prisma/client/runtime/library';
}
declare module '*.wasm' {
const content: any;
export default content;
}
Adding polyfills
export * from 'node:util';
Implementation
Finally, implement the logic equivalent to Prisma Accelerate in src/index.ts.
import { Pool } from '@prisma/pg-worker';
import { PrismaPg } from '@prisma/adapter-pg-worker';
import WASM from '@prisma/client/runtime/query_engine_bg.postgresql.wasm';
import { PrismaAccelerate, PrismaAccelerateConfig, ResultError } from 'prisma-accelerate-local/lib';
import { getPrismaClient } from '@prisma/client/runtime/wasm.js';
const getAdapter = (datasourceUrl: string) => {
const url = new URL(datasourceUrl);
const schema = url.searchParams.get('schema') ?? undefined;
const pool = new Pool({
connectionString: url.toString() ?? undefined,
});
return new PrismaPg(pool, { schema });
};
let prismaAccelerate: PrismaAccelerate;
const getPrismaAccelerate = async ({
secret,
onRequestSchema,
onChangeSchema,
}: {
secret: string;
onRequestSchema: PrismaAccelerateConfig['onRequestSchema'];
onChangeSchema: PrismaAccelerateConfig['onChangeSchema'];
}) => {
if (prismaAccelerate) {
return prismaAccelerate;
}
prismaAccelerate = new PrismaAccelerate({
singleInstance: true,
secret,
adapter: getAdapter,
getRuntime: () => require('@prisma/client/runtime/query_engine_bg.postgresql.js'),
getQueryEngineWasmModule: async () => WASM,
getPrismaClient,
onRequestSchema,
onChangeSchema,
});
return prismaAccelerate;
};
const createResponse = async (result: Promise<unknown>) => {
try {
const response = await result;
return new Response(JSON.stringify(response), {
headers: { 'content-type': 'application/json' },
});
} catch (e) {
if (e instanceof ResultError) {
console.error(e.value);
return new Response(JSON.stringify(e.value), {
status: e.code,
headers: { 'content-type': 'application/json' },
});
}
return new Response(JSON.stringify(e), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}
};
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const prismaAccelerate = await getPrismaAccelerate({
secret: env.PRISMA_ACCELERATE_SECRET,
onRequestSchema: ({ engineVersion, hash, datasourceUrl }) => env.KV.get(`schema-${engineVersion}:${hash}:${datasourceUrl}`),
onChangeSchema: ({ inlineSchema, engineVersion, hash, datasourceUrl }) =>
env.KV.put(`schema-${engineVersion}:${hash}:${datasourceUrl}`, inlineSchema, { expirationTtl: 60 * 60 * 24 * 7 }),
});
const url = new URL(request.url);
const paths = url.pathname.split('/');
const command = paths[3];
const headers = Object.fromEntries(request.headers.entries());
if (request.method === 'POST') {
const body = await request.text();
if (command === 'graphql') {
return createResponse(prismaAccelerate.query({ body, hash: paths[2], headers }));
}
if (command === 'transaction') {
return createResponse(prismaAccelerate.startTransaction({ body, hash: paths[2], headers, version: paths[1] }));
}
if (command === 'itx') {
const id = paths[4];
const subCommand = paths[5];
if (subCommand === 'commit') {
return createResponse(prismaAccelerate.commitTransaction({ id, hash: paths[2], headers }));
}
if (subCommand === 'rollback') {
return createResponse(prismaAccelerate.rollbackTransaction({ id, hash: paths[2], headers }));
}
}
} else if (request.method === 'PUT' && command === 'schema') {
const body = await request.text();
return createResponse(prismaAccelerate.updateSchema({ body, hash: paths[2], headers }));
}
return new Response('Not Found', { status: 404 });
},
};
Deployment
Once you have implemented up to this point, deploy it to Cloudflare Workers.
npm run deploy
Modifications on the Next.js Side
In the Next.js project, you need to configure the following two points:
- Change the Prisma client for edge
- Modify
DATABASE_URLfor Prisma Accelerate
Let's explain each step in order.
1. Changing Prisma Client for Edge
First, change the Prisma client to the version for edge.
Specifically, change the import source of PrismaClient from @prisma/client to @prisma/client/edge.
Also, ensure that withAccelerate is called when creating the PrismaClient.
import { PrismaClient } from "@prisma/client/edge";
import { withAccelerate } from "@prisma/extension-accelerate";
const prismaClientSingleton = () => {
return new PrismaClient().$extends(withAccelerate());
};
declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();
export default prisma;
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;
2. Modifying DATABASE_URL for Prisma Accelerate
Next, set the DATABASE_URL environment variable to the one for Cloudflare Workers.
Generate API_KEY
First, run the following command:
For --secret, specify the same value as the PRISMA_ACCELERATE_SECRET specified in .dev.vars.
For --make, specify a DATABASE_URL such as one from Supabase.
npx prisma-accelerate-local --secret enter_your_secret --make postgres://xxx
When executed, an API_KEY starting with ey will be output, so copy it.
Setting environment variables
Set it in .env.local or a similar file as follows:
DATABASE_URL=prisma://xxxx.workers.dev?api_key=your_api_key
As a point of caution, make sure that the URL scheme is prisma, the host is xxxx.workers.dev, and the query parameter includes api_key.
Conclusion
I introduced a method to build your own Prisma Accelerate on Cloudflare Workers and run Next.js and Prisma in production for free.
By overcoming the limitations of the Edge Runtime environment, it is possible to maintain high performance while keeping costs low. The ability to build a robust service that can handle 3 million accesses per month is a major advantage.
I hope you try this method and find it useful for your projects.
Error Handling
PrismaClientValidationError: Invalid client engine type, please use library or binary
Please run prisma generate again with the --no-engine option.
Unauthorized, check your connection string: {"type":"UnknownJsonError","body":{"Unauthorized":{"reason":"InvalidKey"}}}
The API_KEY is not set correctly. Please check the steps in Generate API_KEY.
References
Discussion