How I use PrismaClient with Qwik on Cloudflare pages

I was playing around with Qwik. The most insanely fast web framework out there. Yes, the most.

And this is mainly because of the philosophy behind Qwik and its creator. Which you can read about here. An excellent read. If you ever liked Angular like me. Same creator.

Qwik apps are instant because there is nothing to do on application startup if the framework is resumable. No hydration, no islands. Just code splitting and lazy-loading components, functions, server functions, and even lazy-loading styles.

So of course we want an instant web app on the edge, right?

So I wanted to build the entire stack for a saas idea my girlfriend and I want to execute, and obviously, I want to put it at the edge!

In the next 8 min, I will show you the small details that could be really challenging if this is your first time with Cloudflare, Vite, Qwik, Prisma or even TypeScript. 📚

And I will tell you how to solve them, too. Enjoy.

Let's analyze the potential problems you'll face.

When working with Prisma at the edge, in this case Cloudflare which, by the way, published some new products and beta releases this month, and even alpha previews of their new database called D1, for example. Or even their new TCP adapter which now allows you to connect to your traditional database just like the old days. Learn more about it here.

But if you don't want to lose the value that the workers offer to your product and most important: your customers.

Then you want to be at the edge, and not just that! You want to use a CloudDB (HTTP) and you want to be resumable. #NeverHydrateAgain.

That's why we want to use Qwik, which is a very different framework in comparison with the most popular Next, Remix, or the smart SvelteKit.

Qwik allows you to not do hydration, they argue that hydration is a hack to recover the APP_STATE and FRAMEWORK_STATE by eagerly executing the app code in the browser. This involves:

  1. downloading component code

  2. executing component code

  3. recovering the WHAT(APP_STATE and FRAMEWORK_STATE) and WHERE to get event handler closure

  4. attaching WHAT (the event handler closure) to WHERE (a DOM element)

All the explanations about WHAT, APP_STATE etc. can be found here.

enter image description here

Which DB should we use?

Last but not least: The Database. What is the best database? Wait, be careful, we can make a better question: Which database is the best, for the edge, specifically for Cloudflare?

Not an easy answer, right? That's why they are building a brand-new native SQL DB (D1).

But we need a production-ready db for our saas apps today, with the ability to use HTTPS communication instead of TCP. There are not so many HTTPS DBs out there yet, but one of the most interesting is Vitess, the open-source database technology that was invented at YouTube in 2010 to solve the scaling issues they faced with their massive MySQL database. The same DB that PlanetScale uses underneath.

I just randomly chose (So many options 🥶) I know I can do some SQL on mysql2.js in a very frictionless way. But hey!, don't forget about type safety and Schema management. Nevertheless, the pick is:

MongoDBAtlas + PrismaProxy + PrismaClient

Cool ha? I also like to work with this group of nice APIs.

If all you have is a hammer, everything looks like a nail.

Well, we already know this won't be easy, but if we achieve the goal, this could be blazingly fast and amazingly powerful. So the trade-off seems to be pretty good.

Prepare the weekend! or a couple of nights 👊🏼 🍕🎧📚

Finally, We are using Cloudflare because of their amazing Edge network, which may be one of the game changers on cloud computing and don't forget that they arrived at first. We want all the benefits of a CDN, with the power of computing code if necessary in a beastly fast way. ⚡️ Yes, we want it all. 😳

So in summary. I'm very sure we are about to deal with:

  • Because Qwik is a server framework, we need to wrap our functions and components inside $

  • PrismaClient and Vite SSR builder are not best friends.

  • MongoDB Atlas does not offer an HTTPS connection with the free plan.

  • A Free Prisma cloud account would be necessary to proxy the connection to your DB via HTTPS.

  • Since Prisma recognizes Cloudflare runtime as a Browser (they are very similar) will need to instantiate PrismaClient isolated.

  • Cloudflare Env variables could not be visible for the PrismaClient

  • And any other random bug that can possibly happen is about to happen today. 🪲

But that's ok. We were made for this. 🐝

Let's install

Here is the link to the repo, so you can go directly to the source and read (which is much faster than reading this post). But if you stay, I'd like to show you the details and explain'em.

You can start an empty project with the tool I used: Vite. Vite is super easy to use and config and is semantically beautiful and maybe Vite poses one of the most active, inclusive, and smart communities. And the tools are just amazingly powerful, it is also a good idea to standardize a bit, so if you have plans to build your own framework, please, do it on Vite.

npm create qwik@latest

This will run a CLI and will generate a brand-new Qwik+ViteSSR project.

We're also going to install some dev tools: npm i prisma --save-dev

And some others for the project: npm i @prisma/client

Also used in this project:

  "devDependencies": {
    "@builder.io/qwik": "^1.1.4",
    "@builder.io/qwik-city": "^1.1.4",
    "@types/eslint": "8.37.0",
    "@types/node": "^20.1.4",
    "@typescript-eslint/eslint-plugin": "5.59.5",
    "@typescript-eslint/parser": "5.59.5",
    "autoprefixer": "^10.4.14",
    "daisyui": "^2.51.6",
    "eslint": "8.40.0",
    "eslint-plugin-qwik": "^1.1.4",
    "postcss": "^8.4.23",
    "prettier": "2.8.8",
    "prisma": "^4.14.1",
    "tailwindcss": "^3.3.1",
    "typescript": "5.0.4",
    "undici": "5.22.1",
    "vite": "4.3.5",
    "vite-tsconfig-paths": "4.2.0",
    "wrangler": "^2.20.0",
    "tiny-invariant": "^1.3.1",
  },
  "dependencies": {
    "@prisma/client": "^4.14.1",
    "zod": "^3.21.4"
  }

How much code can we avoid building or avoid delivering to the client, just by working full-stack? Isn't this nice?

Now the Prisma.Schema

We can pull our database info with Prisma, so we do not need to write a single character of the Schemas (one of the main reasons why I want to use Prisma as a dev-tool):

npx prisma init This will create our schema file at prisma/shcema.prisma let's add the following:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider  = "mongodb"
  url       = env("DATABASE_URL")
  directUrl = env("MONGO_ATLAS")
}
model User {
  name          String?
  email         String  @unique
  id            String  @id @default(auto()) @map("_id") @db.ObjectId
  access_token  String?
  picture       String?
  provider      String?
  refresh_token String?
}

Not much happening here, but for the two URLs: url and directURL.

You don't really need directURL but since Prisma's --data-proxy command doesn't support db push or db pull (more about Prisma Data Proxy here) we need to add it, so we can pull/push from the regular connection while we are developing.

Not bad ha?

So, let's get that two env variables.

Prisma Data Proxy

Yep, a Prisma service. Am I putting so much faith in Prisma?

cloud.prisma.io

Prisma cloud

Once you've related your database link to a proxy and chosen the region, it's time to create a .env file.

PRISMA_PROXY=prisma://aws-us-east-psdfsdflknjsadf
MONGO_ATLAS=mongodb+srv://blissmoasdpojdfsñihasdf

Remember, the Prisma proxy is about to deal as an intermediary between your web app and a TCP connection to your Atlas DB. Not exactly a CloudDB, but we are on the way. 🔥

Prisma Client at the edge

We also need to use a special Prisma Client that knows how to deal with the edge: npx prisma generate --data-proxy

So, we've pulled our database and updated some Models. It's time to generate the client:

npx prisma db push

And then:

npx prisma generate --data-proxy

With this, we are now able to use the import { PrismaClient } from "@prisma/client/edge" inside of our code.

👀 Do not forget to add "postinstall": "prisma generate && prisma generate --data-proxy" into yor scripts inside package.json. This way Prisma generates at build time on deploy.

Let's connect and use the database.

I've created one function to pass the Cloudflare Env variables to the PrismaClient in order to avoid any potential Env problems on Cloudflare, which are really hard to address. Kinda hacky, but useful.

src/db/getDB.ts

import { PrismaClient } from "@prisma/client/edge";

export const getDB = (
  env: QwikCityPlatform["env"] & { get: (arg0: string) => string | undefined }
) => {
  return new PrismaClient({
    datasources: {
      db: {
        url: env.get("DATABASE_URL"),
      },
    },
  });
};

It is important to mention that the import of the regular client is different from the proxy one, this will come from: @prisma/client/edge

This is very important. Please verify if you don't have incorrect imports in your files. 🕵🏽‍♂️

We don't have too much worry about resources with HTTP in comparison to TCP. Not our responsibility anymore… Thanks Prisma? (Is a Prisma proxy something interesting to build? 🤓)

Well, the use of these functions looks like this:

// this's a server function on Qwik:
const createPrismaUser = server$(
  async (data: UserType, prisma: PrismaClient): Promise<UserType> => {
    return (await prisma.user.upsert({
      where: { email: data.email },
      create: data,
      update: data,
    })) as UserType;
  }
);

Disclaimer: I'm very sure this is not the best approach. But I'm very new to Qwik as a lot of my generation of developers, I want to be up-to-date and follow the most exciting open source projects. JS is such a gorgeous and fecund community. Please be patient. And to yourself. 🤓

So, check how I pass the Prisma client to this function: const user = await createPrismaUser(data, getDB(request.env));

export const onGet: RequestHandler = async (request) => {
  const url = new URL(request.url);
  const code = url.searchParams.get("code");
  if (!code) return;
  const { access_token, token_type } = await getToken(code, request);
  const data = await getNewUserData({ access_token, token_type });
  const user = await createPrismaUser(data, getDB(request.env));

  request.cookie.set("userId", String(user.id), {
    maxAge: [7, "days"],
    path: "/",
    httpOnly: true,
    sameSite: "strict",
  });
};

createPrismaUser will get, as a second parameter, the PrismaClient. It's ok to instantiate on every connection. We are dealing with HTTP connections, remember? that's what make any DB a CloudDB.

This Qwik endpoints are just lovely 💜

Vite - Prisma build issue

We've got it all in place, now we need to try to build and see what happens.

One of the main problems, at the building step, is Vite saying:

./prisma/client/index-browser or .prisma/client/edge.

.prisma/client

Not a very fortunate decision, to save the generated client in a hidden folder inside node_modules 😔. So we need to alias it on Vite's config file:

vite.config.ts

export default defineConfig(() => {
  return {
    resolve:{
      alias: {
        ".prisma/client/edge":"./node_modules/.prisma/client/edge.js"
      }
    },
      plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
        preview: {
          headers: {
            'Cache-Control': 'public, max-age=600',
          },
        }
  };})

Every time you find .prisma/client/edge you'll use ./node_modules/.prisma/client/edge.js instead. That's what it does. Yeah, that's a good one, Vite! 🥳😎 Maybe subscribe as a way to say thank you? 😜

Finally, let's deploy

Don't forget to add your Env variables to the Cloudflare console at the settings tab > Environment variables of your project.

I created the project on Cloudflare via their wizard, where I connected my GitHub account and selected my repo.

Cloudflare pages

That's it, you now have it all, I've nothing left to give you today. Come back tomorrow. Maybe one more thing: #NeverHydrateAgain

Abrazo. Bliss.

PlanetScale

Why Qwik?

Check my courses in Spanish

Official Prisma guide for Cloudflare

Prisma Data Proxy

Repo of this project

Hydration is pure overhead

Did you find this article valuable?

Support Héctor BlisS by becoming a sponsor. Any amount is appreciated!