Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't use Prisma client in Next.js middleware, even when deploying to Node.js #21310

Closed
markspolakovs opened this issue Oct 2, 2023 · 41 comments
Labels
bug/2-confirmed Bug has been reproduced and confirmed. kind/bug A reported bug. status/is-preview-feature This feature request is currently available as a Preview feature. team/client Issue for team Client. tech/typescript Issue for tech TypeScript. topic: deployment/vercel topic: edge runtime topic: Next.js middleware topic: Next.js topic: Vercel Edge Middleware
Milestone

Comments

@markspolakovs
Copy link

Bug description

Next.js middleware seems to fail the edge runtime check, even when deployed to Node.js:

Error: PrismaClient is unable to run in Vercel Edge Functions. As an alternative, try Accelerate: https://pris.ly/d/accelerate.
If this is unexpected, please open an issue: https://github.com/prisma/prisma/issues

Wasn't sure if this is a Next or a Prisma bug.

How to reproduce

  1. Clone https://github.com/markspolakovs/next-prisma-repro
  2. Run yarn dev
  3. Visit http://localhost:3000
image

Expected behavior

DB operations to work inside middleware.

Prisma information

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

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

model User {
  id Int @id @default(autoincrement())
}
import { PrismaClient } from "@prisma/client";
import { NextRequest, NextResponse } from "next/server";

const prisma = new PrismaClient();

export async function middleware(req: NextRequest): Promise<NextResponse> {
  const user = await prisma.user.findFirst();
  return NextResponse.next();
}

Environment & setup

  • OS: macOS arm64
  • Database: SQLite
  • Node.js version: v18.17.0

Prisma Version

prisma                  : 5.3.1
@prisma/client          : 5.3.1
Current platform        : darwin-arm64
Query Engine (Node-API) : libquery-engine 61e140623197a131c2a6189271ffee05a7aa9a59 (at node_modules/@prisma/engines/libquery_engine-darwin-arm64.dylib.node)
Schema Engine           : schema-engine-cli 61e140623197a131c2a6189271ffee05a7aa9a59 (at node_modules/@prisma/engines/schema-engine-darwin-arm64)
Schema Wasm             : @prisma/prisma-schema-wasm 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59
Default Engines Hash    : 61e140623197a131c2a6189271ffee05a7aa9a59
Studio                  : 0.494.0
@markspolakovs markspolakovs added the kind/bug A reported bug. label Oct 2, 2023
@Junko-Takeguchi
Copy link

@markspolakovs you should try importing the prisma client like this
import { PrismaClient } from '@prisma/client/edge
for the edge functions.

@aqrln aqrln added bug/1-unconfirmed Bug should have enough information for reproduction, but confirmation has not happened yet. topic: Next.js topic: deployment/vercel tech/typescript Issue for tech TypeScript. team/client Issue for team Client. labels Oct 5, 2023
@Hunam6
Copy link

Hunam6 commented Nov 2, 2023

Same issue here.
There were some previous talk here #9928. But it's quite old, I think the situation has changed.
NextJS middlewares are really useful for performance or authentication with next-auth@beta for example. NextJS's official "book"/tutorial uses a middleware with next-auth.

@millsp
Copy link
Member

millsp commented Nov 7, 2023

@markspolakovs @prisma/client is not able to run in Vercel Edge Middleware (or Vercel Edge Functions), as mentioned in the message. The only way to currently get this working, is to use Prisma Accelerate and change your import to @prisma/client/edge.

@millsp millsp added bug/2-confirmed Bug has been reproduced and confirmed. and removed bug/1-unconfirmed Bug should have enough information for reproduction, but confirmation has not happened yet. labels Nov 7, 2023
@markspolakovs
Copy link
Author

Thanks for confirming @millsp. Is this still true if I'm self-hosting my Next app (using Docker), rather than running it on Vercel?

@millsp
Copy link
Member

millsp commented Nov 7, 2023

Hmm, I am trying to think what we could do to workaround this. Next.js is bundling your project, and it will bundle the separate edge function via the browser field (hence this special error). So if would try a few things to prevent that, since you are running that function on Node.js why pick the browser version anyways? I can imagine two ways to bypass the bundling of browser:

  • I'd try to see if there is any way to mark @prisma/client dependency as external on the edge function. I don't think this is possible though, maybe by fiddling with their webpack or swc config.
  • Probably the easier way to bypass the browser entrypoint being bundled, would be to provide the full path to the generated prisma client. Something like import { PrismaClient } from '../node_modules/.prisma/client/index. This should force Next.js not to use the browser entrypoint and just stick to index.

You may get other hurdles down the line (eg. engine or schema not being copied in the correct locations), happy to assist as needed.

@clemenspeters
Copy link

Pretty annoying. Moving the very same function call out of the middleware and it works.
This was my workaround for now, but I would have preferred to keep the code in the middleware where it was before we started using Primsa. 🙈

@markspolakovs
Copy link
Author

Unfortunately it looks like Next's edge runtime blocks all this trickery: I tried the client/index import that @millsp suggested, and also set unstable_allowDynamic in my middleware config, but Prisma Client still fails to load:

 ⨯ Error [TypeError]: wr1.deprecate is not a function
    at <unknown> (webpack-internal:///(middleware)/../utility/prisma/client/runtime/library.js:388)
    at eval (webpack-internal:///(middleware)/../utility/prisma/client/runtime/library.js:388:22)
    at eval (webpack-internal:///(middleware)/../utility/prisma/client/runtime/library.js:16:30)
    at eval (webpack-internal:///(middleware)/../utility/prisma/client/runtime/library.js:524:128)
    at eval (webpack-internal:///(middleware)/../utility/prisma/client/runtime/library.js:16:30)
    at eval (webpack-internal:///(middleware)/../utility/prisma/client/runtime/library.js:1064:12)
    at (middleware)/../utility/prisma/client/runtime/library.js (file:///Users/marks/code/bowser/server/.next/server/middleware.js:1117:1)
    at __webpack_require__ (file:///Users/marks/code/bowser/server/.next/server/edge-runtime-webpack.js:37:33)
    at fn (file:///Users/marks/code/bowser/server/.next/server/edge-runtime-webpack.js:325:21)
    at eval (webpack-internal:///(middleware)/../utility/prisma/client/index.js:6:324)
    at (middleware)/../utility/prisma/client/index.js (file:///Users/marks/code/bowser/server/.next/server/middleware.js:1095:1)

(I ran runtime/library.js through prettier to help with debugging, hence the line numbers being different.)

It looks like it's getting tripped up on Prisma requiring debug which then requires Node's util module to call util.deprecate - unfortunately I'd imagine that trying to solve this will just lead to a game of whack-a-mole with various Node modules the Edge Runtime doesn't support.

I found a feature request on the Next side to allow configuring middleware to use the Node runtime, but with no response from the Next team: vercel/next.js#38989

@ChrisB1123
Copy link

ChrisB1123 commented Dec 5, 2023

A solution for anyone else who ran into this error just trying to set up a project - I believe you can just use jwt sessions, rather than database sessions.

export const config = {
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },      // Add this line
  callbacks: ...
  providers: ...,
} satisfies NextAuthConfig;

@mamlzy

This comment was marked as outdated.

@satyaprakash-yadav

This comment was marked as off-topic.

@iMerica
Copy link

iMerica commented Feb 22, 2024

Why would querying the DB be any different in middleware than in a server component? To the DB client, it's all just a Node.js runtime.

I hope I'm wrong, but this seems like a deliberate limitation to sell some commercial feature. Which is really outside the spirit and ethos of open source (see OSI).

 ⨯ PrismaClient is unable to run in Vercel Edge Functions or Edge Middleware. As an alternative, try Accelerate: https://pris.ly/d/accelerate.

@janpio
Copy link
Member

janpio commented Feb 22, 2024

Vercel Edge Middleware is not a Node.js runtime, but the Vercel Edge Runtime. Prisma currently can not run in that environment as it does not allow talking to databases via the normal database drivers using the normal methods like TCP connections - which Prisma usually uses.

@iMerica
Copy link

iMerica commented Feb 22, 2024

@janpio Might be true, but that is not a relevant fact in my case since I don't use Vercel. I deploy Next.js apps as Docker containers that run in Kubernetes. The base Docker image is always node:21-alpine.

@janpio
Copy link
Member

janpio commented Feb 22, 2024

Then you should open a bug report instead of assuming ill intent, so we can look into that, because that of course should not happen. Obviously the error message is incorrect, and we want to look into that and fix it. (I am not fully sure if that is also the case above, so an additional bug issue with information and optimally a reproduction would be very helpful.)

PS: We are also currently working on, and have a private Early Access running, for support of Vercel Edge Middleware: #21394

@goleary
Copy link

goleary commented Mar 11, 2024

IMO this issue is about this error message appearing even and preventing usage of prisma in middleware when not using Vercel in any way, not that the prisma client can't be used in the vercel edge runtime.

@janpio The original reproduction from this issue should do the trick.

@janpio
Copy link
Member

janpio commented Mar 12, 2024

The Next.js documentation has the answer to the problem:

image

Runtime

Middleware currently only supports the Edge runtime. The Node.js runtime can not be used.

Source https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime

So even when you run your Next.js app locally via Node.js, it unfortunately still runs the middleware via the Edge middleware instead.

@janpio
Copy link
Member

janpio commented Mar 12, 2024

Fortunately, we just released Prisma ORM version 5.11.0 which includes a preview feature for Edge Functions support for Vercel Edge Functions and Middleware (and Cloudflare Workers and Pages) in Prisma ORM 🥳

Please give it a try, and let us know how it goes! If you encounter any problems, please create a new bug report issue, or if the problem is driver adapter specific, use the feedback discussions for @prisma/adapter-neon, @prisma/adapter-planetscale, or @prisma/adapter-libsql / Turso 🙇


With that I was able to upgrade the reproduction from the original issue description to Prisma 5.11.0, and use a PostgreSQL database from Neon (or Vercel Postgres): markspolakovs/next-prisma-repro#1 (Unfortunately the Edge Runtime does not support reading local files, so using SQLite - which is a local file - is out of the question, so I had to switch to using PostgreSQL.)

@goleary
Copy link

goleary commented Mar 22, 2024

@janpio Thanks for the update, instructions, and the example PR!

I upgraded prisma in my project and was also able to use prisma via the neon adapater in my middleware function...slick!

@defrex
Copy link

defrex commented Mar 26, 2024

Stoked to have this working! However, I can no longer find a way to access a local postgres instance from the middleware.

We use Neon in production, but for local development it's more convenient to use a local postgres instance. Has anyone found a workaround for this?

@janpio
Copy link
Member

janpio commented Mar 26, 2024

Our own tests use a "proxy" from Neon in front of a PostgreSQL database, which is the same component that runs remotely as the endpoint for the Neon serverless database driver:

neon_wsproxy:
image: ghcr.io/neondatabase/wsproxy:latest
environment:
APPEND_PORT: 'postgres-16:5432'
ALLOW_ADDR_REGEX: '.*'
LOG_TRAFFIC: 'true'
LOG_CONN_INFO: 'true'
ports:
- '5488:80'
depends_on:
- postgres-16
restart: unless-stopped
healthcheck:
test: ['CMD', 'nc', '-z', '127.0.0.1', '80']
interval: 5s
timeout: 2s
retries: 20
Maybe that is a viable approach for you @defrex?

@defrex
Copy link

defrex commented Mar 27, 2024

@janpio awesome, this seems like a great approach.

I can't quite get it to work, however. Running the container is simple enough, however the Pool instance seems to require port 443, even when I set a specific port and set ssl: false.

Are you doing anything special at PrismaClient initialization time?

Something as simple as this doesn't appear to work for me

  const pool = new Pool({ connectionString: 'postgresql://username:@localhost:5488/dbname' })
  const adapter = new PrismaNeon(pool)
  const prisma = new PrismaClient({ adapter })

This results in

    Error: connect ECONNREFUSED 127.0.0.1:443
        at createConnectionError (node:net:1634:14)
        at afterConnectMultiple (node:net:1664:40) {
      errno: -61,
      code: 'ECONNREFUSED',
      syscall: 'connect',
      address: '127.0.0.1',
      port: 443
    }

@janpio
Copy link
Member

janpio commented Mar 27, 2024

See here @defrex:

if (driverAdapter === AdapterProviders.JS_NEON) {
const { neonConfig, Pool } = require('@neondatabase/serverless') as typeof import('@neondatabase/serverless')
const { PrismaNeon } = require('@prisma/adapter-neon') as typeof import('@prisma/adapter-neon')
neonConfig.wsProxy = () => `127.0.0.1:5488/v1`
neonConfig.webSocketConstructor = WebSocket
neonConfig.useSecureWebSocket = false // disable tls
neonConfig.pipelineConnect = false
const pool = new Pool({
connectionString: datasourceInfo.databaseUrl,
})
return { adapter: new PrismaNeon(pool), __internal }
}

(If you figure it out, I would appreciate a blog post so I can link to that one in the future 😆)

@defrex
Copy link

defrex commented Mar 28, 2024

I'm happy to report this strategy worked! I don't know about dusting off the blog, but here is a quick summary for future travellers.

Local Neon-Enumating Websocket Proxy

Add the @neondatabase/wsproxy to your docker-compose.yaml.

In this example db is the name of my postgres container, swap it out for your own!

  neon_wsproxy:
    container_name: neon-wsproxy
    image: ghcr.io/neondatabase/wsproxy:latest
    environment:
      APPEND_PORT: 'db:5432'
      ALLOW_ADDR_REGEX: '.*'
      LOG_TRAFFIC: 'true'
      LOG_CONN_INFO: 'true'
    ports:
      - '5488:80'
    depends_on:
      - db
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'nc', '-z', '127.0.0.1', '80']
      interval: 5s
      timeout: 2s
      retries: 20

Then, initialize your PrismaClient and PrismaNeon adapter with a few extra touches.

// Polyfill for WebSocket in Node.js
if (global.WebSocket === undefined) { // don't break Edge, where `global.WebSocket` pre-exists
  const ws = require('ws')
  neonConfig.webSocketConstructor = ws
}

let databaseUrl = process.env.DATABASE_URL

// Configure the Neon adapter to connect to the local Neon proxy
if (databaseUrl.includes('localhost')) {
  // we edit the direct `DATABASE_URL` here, so `.env` can still be used by the `prisma` CLI
  databaseUrl = editDatabaseUrl(databaseUrl, { port: 5488 })

  neonConfig.wsProxy = () => `127.0.0.1:5488/v1`
  neonConfig.useSecureWebSocket = false
  neonConfig.pipelineConnect = false
}

const pool = new Pool({ connectionString: databaseUrl })
const adapter = new PrismaNeon(pool)
const prisma = new PrismaClient({
  adapter,
  datasources: {
    db: { url: databaseUrl },
  },
})

Et voila! This prisma instance shout be GTG for use in the Edge runtime, with either a local or Neon-hosted postgres instance.

@petercunha
Copy link

This is very annoying, I dont use Neon or Planetscale, or even Vercel or Cloudflare edge functions.

I just want to roll my own middleware and my own database on my own server. It makes no sense why I cant just invoke the client locally. Why do we have to use a special Neon adapter or have a cloudflare account or use Accelerate? Why cant we just invoke local stuff in our middleware? This makes no sense. Overengineered garbanzo.

@iMerica
Copy link

iMerica commented Mar 30, 2024

@petercunha

As far as I can tell this issue was a product decision made by Vercel. It seems like they have completely lost the plot -- forgetting that they are the stewards of an open source project web framework (Next.js) - not a Vendor web framework called Vercel.js. It's not over-engineered, it's wrongfully engineered.

If it was "open" this middleware would be interoperable in any cloud/server environment instead of exclusively on Vercel. Sadly, Next.js is becoming fauxpen source.

@petercunha
Copy link

@iMerica you are correct. I thought this was Prisma's fault at first, but it turns out that edge middleware is a vendor lock in strategy by Vercel.

Unfortunately the only way around this is to build a proxy server in front of Next.js that handles middleware.

There is a thread about this on Next's repo that has been open for over a year with no updates: vercel/next.js#46722

Looks like Vercel has no intention of allowing self hosted middleware any time soon.

@defrex
Copy link

defrex commented Apr 2, 2024

@petercunha @iMerica I can't speak to the business motivations here (I don't work for Vercel or Prisma), but there is an actual technical issues at play.

Middleware runs on an Edge runtime, same as Cloudflare Workers. I suspect this choice was made for performance reasons. Ie if you're going to run a separate process before every request, you'd better make it a fast one. But by separating the process out, you can serve static or cached assets to the client, without needing to make a full server-render run. Otherwise Next.js with auth would have similar perf characteristics to Rails or Django.

However, the Edge runtime only has access to web-standard APIs, without any Node.js extensions. This means it can't use unix sockets or tcp to connect to the database. That leaves HTTP or WebSockets. The request/response overhead makes using HTTP prohibitive.

Neon and Planetscale both support WebSocket connections out of the box, which is why they can be supported. The code above shows how you can use a proxy service to allow a WebSocket connection to a regular Postgres instance, this could easily be used in production if you're willing & able to run that container.

@iMerica
Copy link

iMerica commented Apr 3, 2024

@defrex

Respectfully you are mistaken. This problem was created from the decision to redefine middleware as "edge middleware" which is a complete deviation of what "middleware" means in the context of a web framework. Vercel has decided to lock a basic feature of web frameworks (middleware that is executed before/after a request) behind a vendor-specific implementation. It's like a scope creep that omits the original feature.

If there was "edge middleware" and regular middleware, than this entire thread would not exist. If they had released only "Edge Middleware" than this Github Issue would be a Feature Request to "Add normal middleware" instead of a bug because we wouldn't have a false expectation. I'm using the term "middleware" as its conventionally used in Nest.js, Django, Laravel, Rails and other frameworks.

@janpio
Copy link
Member

janpio commented Apr 3, 2024

Here is the Next.js documentation that highlights what is causing this challenge:

Middleware currently only supports the Edge runtime. The Node.js runtime can not be used.

https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime

Sorry, but as far as I know there is nothing Prisma can do about that. (We have been talking internally about if we can at least document how to set up the Neon and PlanetScale proxies for local testing.)

@bilalmohib
Copy link

Hi @janpio Thats why I am now migrating to use drizzle orm.

@janpio
Copy link
Member

janpio commented Apr 9, 2024

Let me know if and how Drizzle has solved that problem. If they have, we have overlooked something and can work on a similar solution of course.

@jkomyno
Copy link
Contributor

jkomyno commented May 30, 2024

Hi @markspolakovs, @bilalmohib and others, can you please retry with the following (unstable) version of prisma and @prisma/client?
5.15.0-integration-client-dynamic-wasm-imports.1.

We believe that such version contains the fix to this issue.
We expect this to be released in the next stable Prisma version, 5.15.0, next week.
Thanks!

@jkomyno jkomyno added this to the 5.15.0 milestone May 31, 2024
@entrptaher
Copy link

entrptaher commented Jun 3, 2024

I have found a minimal workaround for now. This is to have the prisma on a api and handling the request from there.

image

// middleware.ts

import { NextRequest, NextResponse } from 'next/server'
import { isAuthenticated } from './lib/auth'

export const config = {
  matcher: '/api/:path*',
}

export async function middleware(request: NextRequest) {
  // this api has the auth related info
  if (request.nextUrl.pathname.startsWith('/api/prisma')) {
    return NextResponse.next()
  }

  // Call our authentication function to check the request
  if (!(await isAuthenticated(request))) {
    // Respond with JSON indicating an error message
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

And then one api,

// lib/auth.ts
import { NextRequest } from "next/server";

export async function isAuthenticated(request) {
  // do stuff like checking if the user pass etc matches with the api
  fetch(`${process.env.NEXTJS_APP_URL}/api/prisma`)
    .then((data) => data.json())
    .then(console.log);
  return true;
}
// /api/prisma/route.ts

// EDIT: move this to somwehere else like lib/db.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient()

export async function GET(request: Request) {
  const posts = await prisma.post.findMany()
  return new Response(JSON.stringify(posts))
}

Definitely not the prettiest solution, but still many times better than having same code/function in 10 different api files.

@jkomyno
Copy link
Contributor

jkomyno commented Jun 3, 2024

Hi @entrptaher, my recommendation is to not expose Prisma to the Next.js /api middlewares.
Rather, declare it somewhere like src/db/prisma.ts (like done here).

To see how Prisma can be then used for API endpoints, you may refer e.g. to this Next.js Auth example.

Regardless, the important thing for Edge Middlewares to work correctly with Prisma is to either wait for Prisma 5.15.0 (which we'll release tomorrow) or try the integration version posted above,
as these versions fix known problems such as the one OP presented in this issue :)

@entrptaher
Copy link

Hi @entrptaher, my recommendation is to not expose Prisma to the Next.js /api middlewares. Rather, declare it somewhere like src/db/prisma.ts (like done here).

To see how Prisma can be then used for API endpoints, you may refer e.g. to this Next.js Auth example.

Regardless, the important thing for Edge Middlewares to work correctly with Prisma is to either wait for Prisma 5.15.0 (which we'll release tomorrow) or try the integration version posted above, as these versions fix known problems such as the one OP presented in this issue :)

Thank you for the reply. I do not use or suggest prisma from api route directly, the above example is just a proof of concept to show a workaround.

@CodeZak
Copy link

CodeZak commented Jun 4, 2024

@jkomyno I tried upgrading to 5.15.0-integration-client-dynamic-wasm-imports.1 (both prisma and prisma/client) but to no avail, currently, the only solution that works is using Prisma accelerate.

@jkomyno
Copy link
Contributor

jkomyno commented Jun 4, 2024

Hey @CodeZak, can you please share your own reproduction, possibly in a new issue? I wonder whether you bumped into a different edge case than the one we have fixed. Thanks!

@jkomyno
Copy link
Contributor

jkomyno commented Jun 4, 2024

Also, @CodeZak, did you re-run prisma generate after installing the new Prisma Client version?

@jkomyno
Copy link
Contributor

jkomyno commented Jun 4, 2024

We fixed this issue via #24312, and the fix will be part of the newest Prisma version, 5.15.0, coming out today 🚀

Though I will close this issue right now, please keep the feedback coming below. Thanks!

@jkomyno jkomyno closed this as completed Jun 4, 2024
@CodeZak
Copy link

CodeZak commented Jun 4, 2024

Also, @CodeZak, did you re-run prisma generate after installing the new Prisma Client version?

yeah, i did that,
I have pushed a new repo that you can take a look at to investigate the issue: https://github.com/CodeZak/NextAuth5
The code runs Prisma on the middleware through the authConfig file.

@janpio
Copy link
Member

janpio commented Jun 5, 2024

Thanks for the reproduction @CodeZak, I turned this into a new issue so we can properly investigate: #24430

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug/2-confirmed Bug has been reproduced and confirmed. kind/bug A reported bug. status/is-preview-feature This feature request is currently available as a Preview feature. team/client Issue for team Client. tech/typescript Issue for tech TypeScript. topic: deployment/vercel topic: edge runtime topic: Next.js middleware topic: Next.js topic: Vercel Edge Middleware
Projects
None yet
Development

No branches or pull requests