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:
- Cloudflare account.
- Supabase accout for the database, but any Postgres connecction string should work.
- 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.