Cloudflare workers is one of the things that I love but at the same time didn't like or wanted to use, I did use it with simple project here/there as a geo api or as a landing page to many of my unfinished projects, but this has change when I learned about Hono framework, which is a web framework that was built by one of cloudflare employees.

Using Hono framework I was able to build manythings in the past few days, which I was not sure if they can be done or not, I mean life and work has taken most of my time to look into that πŸ˜… ...

So today I'll exmplain how you can use Hono with Cloudflare, Prisma orm and Postgres all at once, also I'll explain how you can build small middlewares that will simplify your life a bit.

What do you need?

I assume you know most of the technologies I'll list here or at least you read about them, but we do need to have a few things:

  1. Cloudflare account.
  2. Supabase accout for the database, but any Postgres connecction string should work.
  3. knowladge in Javascript and having nodejs installed in your computer.

This is not a nodejs or javascript article, so you will notice that I've skipped a lot of the details, which you can find by simple search on the internet.

The full code for this can be found on github at this repository zaherg/cloudflare-workers-prisma-postgres-example so feel free to see the code and read it in case you like reading code instead.

Creating our first Cloudflare worker application

Creating your first cloudflare worker application can be as simple as running the following command and answer the questions prompts

$ npm create cloudflare@latest prisma-example

Once you run the command you will be asked four questions:

What type of application do you want to create?
For this question our answer will be the first answer: "Hello World" Worker

Do you want to use TypeScript?
I know this one is like a personal preference, but lets say : Yes

Do you want to use git for version control?
Duah for sure we do, so the answer is:  Yes

Do you want to deploy your application?
I mean we could, but lets delay this to later, so the answer is : No

And we are done we have created our first cloudflare worker

β”‚ Navigate to the new directory cd prisma-example
β”‚ Run the development server npm run start
β”‚ Deploy your application npm run deploy
β”‚ Read the documentation https://developers.cloudflare.com/workers
β”‚ Stuck? Join us at https://discord.cloudflare.com

Installing required packages

Now that we have our worker project ready we need to install the required packages for our project

$ npm install hono pg @prisma/adapter-pg @prisma/client

These are the one we need to run the project on production, but there are some other pacakges that we need to install for development

$ npm install --save-dev @types/pg dotenv-cli prisma

Wrangler tweaks

Before we continue we need to add some configuration to our wrangler.toml file, these configurations will help us run our code and being able to communicate with our postgres database.

First of all, find the following line

compatibility_flags = ["nodejs_compat"]

and replace it with

node_compat = true

Something I noticed is that running the dev server will use a different port everytime, so to use a specific port we can add the following, and feel free to change the port as you wish

[dev]
port = 5000

Environment/Secrets

There are two ways to define your environment variables, either as encrypted secrets or as plain text, for sure the first one is what we need when defining our database connection urls, so this is what we will use.

For local development, we can create a file named .dev.vars and put those constants there and wrangler will pick them up automatically, but sadly prisma does not read this file, and this is why we installed dontenv-cli so we can instruct prisma to read the environment variables from that file specifically.

On production, you can use the wrangler command to add those secrets and use them, the command would be something like

# When running this command, you will be prompted to input the secret’s value:
$ npx wrangler secret put DIRECT_URL
$ npx wrangler secret put DATABASE_URL

our .dev.var will have a values like any other .env file you ever used

# Whcn connecting  to Supabase via connection pooling with Supavisor
# remember to add ?pgbouncer=true to the end of the connection string.
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public?pgbouncer=true"
# Direct connection to the database. Used for migrations.
DIRECT_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

Configure Prisma

We have installed prisma but we didn't generate anything yet, so our first thing we need to do is to run 

$ npx prisma init

This will create the prisma folder and the prisma schema file that we need to use, but we still need to do some changes before we continue, we need to tell prisma to use the driver and so open the schema.prisma file that was generated and update the client to add the driver adapeter and the datasource to add  the directurl so they will look like this

generator client {
    provider        = "prisma-client-js"
    previewFeatures = ["driverAdapters"]
}

datasource db {
    provider  = "postgresql"
    url       = env("DATABASE_URL")
    directUrl = env("DIRECT_URL")
}

Our work didn't end yet, we still need to do a few changes to our package.json file so we can tell prisma to read the environment values from our .dev.vars file, so open the package.json file and add the following to the scripts section

"db:generate": "npm run env prisma generate",
"db:migrate:deploy": "npm run env prisma migrate deploy",
"db:migrate:dev": "npm run env prisma migrate dev",
"db:seed": "npm run env prisma db seed",
"env": "dotenv -e ./.dev.vars --",
"prisma": "npm run env prisma",

These commands are here to help you deal with prisma commands easily, the most important ones are prisma and env  .

You might ask how you can run other commands when needed, its as simple as

npm run prisma -- migrate -- --help

I know its a bit strange, but feel free to run  it this way instead

npx dotenv -e ./.dev.vars -- prisma migrate --help

both commands work the same.

Now you can edit the prisma schema file and add yuor own models for later use, I assume you know how to use prisma right?

Building our first route

Now that we have everything ready lets build our first route by the help of Hono framework.

Open the index.ts file and delete evetyhing inside of it, then add the following code

import { type Context, Hono } from 'hono';

const app = new Hono();

app.get('/', (ctx: Context) => {
    return ctx.json({ message: 'hello' });
});

export default app;

save the file and run npm run dev and visit http://localhost:5000 and here you have got your first route up and running  πŸŽ‰ and you will recive the json response you instrcuted hono to return

Building our first middleware

To connect to our database we will need to add the following code to each and every route we need to use prisma within, which is a bit annoying especially if you wanted to change something, for example if you decided to use Cloudflare hyperdrive instead or connect to Cloudflare D1 πŸ€·β€β™‚οΈ

const connectionString = ctx.env.DATABASE_URL;
const pool = new Pool({ connectionString });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });

The best option is to build your own middleware that you can use whenever you need a database connection, and then when you need to change anything all you have to do is modify that middleware.

Luckely, hono makes creating middlewares simple, the following code will create a middleware that will create a prisma connection if it was not present yet

import type { Context, MiddlewareHandler } from 'hono';
import { createMiddleware } from 'hono/factory';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '@prisma/client';
import { Pool } from 'pg';

export const prisma = (): MiddlewareHandler =>
    createMiddleware(async (ctx: Context, next: any) => {
        if (!ctx.get('prisma')) {
            const connectionString = ctx.env.DATABASE_URL;
            const pool = new Pool({ connectionString });
            const adapter = new PrismaPg(pool);
            ctx.set('prisma', new PrismaClient({ adapter }));
    }
        await next();
    }
);

Save this code in a file called prisma.ts in a directory called middlewares inside src and use it as the following inside our index.ts file

import { prisma } from '@/middlewares/prisma';

app.use('api/*', prisma());

Now any route that will be part of the api request will have the prisma instance ready to use.

The users route

There are many way to create a route in hono the simplest is to have everything inside the index.ts file, but if you have more than one route things will get messy very fast, so you have another option by creating a separate file then use it, and this is what we will do.

Create a file inside a directory called routes inside your src directory and call it users.ts (feel free to name it whatever you want) and add the following code

import { type Context, Hono } from 'hono';


const app = new Hono()
    .get('/', async (ctx: Context) => {
        const prisma = ctx.get('prisma');
    const records = await prisma.user.findMany({
        orderBy: { id: 'asc' },
    });
    return ctx.json(records);
    })
    .get('/:id{[0-9]+}', async (ctx: Context) => {
    const prisma = ctx.get('prisma');
    const id = Number.parseInt(ctx.req.param('id') || '0');
    const record = await prisma.user.findUnique({
        where: { id },
    });
    if (!record) {
        return ctx.notFound();
    }
    return ctx.json(record);
    });

export default app;

For sure you still can use the post/delete methods, but I am trying to simplify the code a bit as the post has become so long, the main thing you need to notice is that we are using the context get function to get the prisma instance and use it directly without even mention the middleware.

Now in our index.ts file we can just use it like the following

import users from '@/routes/users';

app.basePath('api')
    .route('/users', users);

We import the route and tell hono that we need it as part of the users group.

Assuming you have some data in your database and you are ready to deploy your application you can run the deploy commnad which will deploy it to cloudflare workers

$ npm run deploy

but if you haven't yet used Wrangler before, you will be prompted to login to Cloudflare via the browser and give wrangler the required permissions to do its magic.