Databases and the Jamstack: Where Prisma Fits

The Jamstack model—serving pre-rendered static content via a CDN and handling dynamic features through APIs and serverless functions—has become a popular way to build performant, cost-effective sites. JavaScript frameworks like Next.js and Gatsby are common tools for this approach, and they pair well with Git-based deployment platforms such as Vercel and Netlify.

The appeal is straightforward: pre-rendering content and caching responses at the edge minimizes load times, and because you aren’t paying for an always-on dedicated server, operational costs stay low. But one recurring challenge is database access. Prisma, an open-source ORM for JavaScript and TypeScript, was designed to address exactly that.

Prisma interprets a schema defined in its own syntax and generates a type-safe client with CRUD methods for create, read, update, and delete operations. It manages database connections, including pooling, and handles migrations. Supported databases include PostgreSQL, MySQL, SQL Server, and SQLite, with MongoDB currently in preview.

Here is how basic user CRUD might look with Prisma’s client:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

const user = await prisma.user.create({
  data: {
    name: Sam,
    email: '[email protected]',
  },
})

const users = await prisma.user.findMany()

const updateUser = await prisma.user.update({
  where: {
    email: '[email protected]',
  },
  data: {
    email: '[email protected]',
  },
})

const deleteUser = await prisma.user.delete({
  where: {
    email: '[email protected]',
  },
})

The corresponding Prisma schema for that project would define the User model like so:

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

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

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
}

Where Prisma Makes Sense

For JavaScript-based Jamstack projects, data is needed in two contexts: during the static page pre-rendering phase and inside API routes. Prisma is well suited to both.

Take a blog with a reaction system. An API route can use Prisma’s create method to register a new reaction, and another route can call findMany to fetch and return the current counts. Similarly, a landing page can be pre-rendered with statistics pulled directly from the database using Prisma’s reading methods.

That said, there are cases where Prisma is overkill. If your solution only requires a single database table—for example, a newsletter signup box or a contact form—introducing Prisma and a full relational database adds unnecessary development complexity. Stick with simpler tools for those tasks.

Why Not Something Else?

Several alternatives exist for database access in the Jamstack, and each has trade-offs.

Cloud Database Services

Services like Airtable provide a database-like platform accessed via a REST API. They are convenient for prototyping, but they come with costs and performance concerns. Airtable’s Pro tier, for instance, cost $671.54 for a small team at Hack Club in a single month. Hosting an equivalent PostgreSQL database on Heroku, by contrast, costs $9 per month. And because these services sit as a middleman between your application and the underlying database, they add latency that you cannot optimize away. Spending that money on these services may be justified by their UI and API, but Prisma Studio’s interface is a credible counterargument.

Raw SQL

If you are running your own database, why not just use node-postgres or the equivalent driver for your database? For many, the answer is developer experience. Prisma generates a client that is fully type-safe, deriving types from your schema. That means database queries are validated at compile time, preventing a whole class of type errors. Even without TypeScript, Prisma’s Visual Studio Code extension provides autocomplete, linting, and formatting. Community plugins bring the Prisma Language Server to Emacs, Neovim, JetBrains IDEs, and Nova.

Other ORMs

TypeORM is a solid alternative, and choosing between ORMs often comes down to preference. For many developers, Prisma wins on three grounds: the depth of its documentation (particularly the CRUD reference), its broader ecosystem tooling like Prisma Migrate and Prisma Studio, and an active community with events and a public Slack.

Practical Integration in Jamstack Projects

Prisma works differently depending on the framework you choose. The following examples show how to wire it up across a few popular options.

Within Next.js

A key rule for Next.js: Prisma must run server-side. That means it is restricted to getStaticProps, getServerSideProps, and API routes.

import prisma from '../../../lib/prisma'
import { getSession } from 'next-auth/client'

function getRandomNum(min, max) {
  return Math.random() * (max - min) + min
}

export async function getRedemptions(username) {
  let allRedemptions = await prisma.user.findMany({
    where: {
      name: username,
    },
    select: {
      Redemptions: {
        select: {
          id: true,
          Stickers: {
            select: { nickname: true, imageurl: true, infourl: true },
          },
        },
        distinct: ['stickerId'],
      },
    },
  })
  allRedemptions = allRedemptions[0].Redemptions.map(x => ({
    number: getRandomNum(-30, 30),
    ...x.Stickers,
  }))
  return allRedemptions
}

export default async function RedeemCodeReq(req, res) {
  let data = await getRedemptions(req.query.username)
  res.send(data)
}

The code demonstrates that the standard import path (../../../lib/prisma) differs from the usual one.

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

This is a quirk of Next.js’ live refresh system, so Prisma recommends importing the client from a dedicated file rather than instantiating it directly in each module.

Inside Redwood

Redwood is less a pure Jamstack framework and more a full-stack approach inspired by it. Prisma is built into every new Redwood project, with the schema living in api/db/schema.prisma. You don’t interact with Prisma’s client directly; instead, data access goes through GraphQL mutations and queries.

const CREATE_TODO = gql`
  mutation AddTodo_CreateTodo($body: String!) {
    createTodo(body: $body) {
      id
      __typename
      body
      status
    }
  }
`

For a todo item, the underlying Prisma model is simple:

model Todo {
  id     Int    @id @default(autoincrement())
  body   String
  status String @default("off")
}

On the frontend, you trigger that mutation with the useMutation hook from @redwoodjs/web, which is based on Apollo’s GraphQL client:

const [createTodo] = useMutation(CREATE_TODO, {
    //  Updates Apollo's cache, re-rendering affected components
    update: (cache, { data: { createTodo } }) => {
      const { todos } = cache.readQuery({ query: TODOS })
      cache.writeQuery({
        query: TODOS,
        data: { todos: todos.concat([createTodo]) },
      })
    },
  })

  const submitTodo = (body) => {
    createTodo({
      variables: { body },
      optimisticResponse: {
        __typename: 'Mutation',
        createTodo: { __typename: 'Todo', id: 0, body, status: 'loading' },
      },
    })
  }

Redwood can generate the GraphQL SDLs and services directly from your Prisma schema with the scaffold command, e.g., yarn rw g sdl Todo.

On Cloudflare Workers

Cloudflare Workers is a popular platform for edge-hosted APIs, but it does not support TCP connections, which the standard Prisma Client relies on. The Prisma Data Proxy solves this. After setting up a Prisma Cloud Platform account, you receive a connection string starting with prisma://. Add that to your .env file:

DATABASE_URL="prisma://aws-us-east-1.prisma-data.com/?api_key=•••••••••••••••••"

Then, generate the client with a special flag:

PRISMA_CLIENT_ENGINE_TYPE=dataproxy npx prisma generate

Database requests are proxied through, and the rest of your Prisma code remains unchanged. It is not a perfect solution, but it is a serviceable way to get database access on Cloudflare Workers.

The Bottom Line

For developers working in JavaScript or TypeScript who need database access in a Jamstack context, Prisma offers a strong combination of developer experience, tooling, and performance. Each framework integrates it a little differently—Next.js expects it server-side, Redwood routes it through GraphQL, and Cloudflare Workers require a data proxy—but the result is a type-safe, direct path to your data in every case.