Vercel Turso integration returning 401 for `@libsql/client`
Description
Hi!
When accessing data from my site hosted on Vercel, I'm getting a 401 HTTP status code being thrown from the @libsql/client and I have no clue why. When I connect to the database locally, I simply cannot reproduce the issue.
I tried recreating the database, recreating the group, and tweaking the Drizzle configuration, but none of it seemed to work. I have no idea what's throwing this exception and why the request from Vercel isn't getting authorized properly.
If more information is necessary, I'd be more than happy to provide this!
Thanks in advance!
More details
Stacktrace
LibsqlError: SERVER_ERROR: Server returned HTTP status 401
at mapHranaError (file:///var/task/node_modules/.pnpm/@[email protected]/node_modules/@libsql/client/lib-esm/hrana.js:268:16)
at file:///var/task/node_modules/.pnpm/@[email protected]/node_modules/@libsql/client/lib-esm/http.js:76:23
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async LibSQLPreparedQuery.get (file:///var/task/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected][email protected]_beykhe332nz34cv7xpk57h4rsy/node_modules/drizzle-orm/libsql/session.js:155:18)
at async login (file:///var/task/build/server/nodejs-eyJydW50aW1lIjoibm9kZWpzIn0/index.js:505:17)
at async file:///var/task/build/server/nodejs-eyJydW50aW1lIjoibm9kZWpzIn0/index.js:1955:20
at async Object.callRouteAction (/var/task/node_modules/.pnpm/@[email protected][email protected]/node_modules/@remix-run/server-runtime/dist/data.js:36:16)
at async /var/task/node_modules/.pnpm/@[email protected]/node_modules/@remix-run/router/dist/router.cjs.js:4719:19
at async callLoaderOrAction (/var/task/node_modules/.pnpm/@[email protected]/node_modules/@remix-run/router/dist/router.cjs.js:4785:16)
at async Promise.all (index 2) {
code: 'SERVER_ERROR',
rawCode: undefined,
[cause]: HttpServerError: Server returned HTTP status 401
at errorFromResponse (file:///var/task/node_modules/.pnpm/@[email protected]/node_modules/@libsql/hrana-client/lib-esm/http/stream.js:352:16)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
status: 401
}
}
package.json
{
"name": "[REDACTED]",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"build": "remix vite:build",
"dev": "run-p dev:*",
"dev:app": "remix vite:dev",
"dev:turso": "turso dev --db-file './drizzle/db/data.db'",
"dev:drizzle": "drizzle-kit studio",
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
"start": "remix-serve ./build/server/index.js",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "dotenv -e .env -- tsx ./drizzle/migrate.ts",
"db:seed": "dotenv -e .env -- tsx ./drizzle/seed.ts",
"db:studio": "drizzle-kit studio",
"typecheck": "tsc",
"format": "prettier --check --cache .",
"prepare": "husky"
},
"dependencies": {
"@conform-to/react": "^1.2.2",
"@conform-to/zod": "^1.2.2",
"@epic-web/invariant": "^1.0.0",
"@epic-web/totp": "^2.0.0",
"@libsql/client": "^0.14.0",
"@paralleldrive/cuid2": "^2.2.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@react-email/components": "^0.0.25",
"@remix-run/node": "^2.13.1",
"@remix-run/react": "^2.13.1",
"@remix-run/router": "^1.20.0",
"@remix-run/serve": "^2.13.1",
"@t3-oss/env-core": "^0.11.1",
"@vercel/remix": "^2.13.1",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"drizzle-orm": "^0.35.3",
"input-otp": "^1.2.4",
"isbot": "^5.1.17",
"lucide-react": "^0.453.0",
"react": "rc",
"react-dom": "rc",
"remix-themes": "^1.5.1",
"resend": "^4.0.0",
"sonner": "^1.5.0",
"spin-delay": "^2.0.1",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@faker-js/faker": "^9.0.3",
"@remix-run/dev": "^2.13.1",
"@total-typescript/tsconfig": "^1.0.4",
"@types/bcryptjs": "^2.4.6",
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc",
"autoprefixer": "^10.4.20",
"dotenv-cli": "^7.4.2",
"drizzle-kit": "^0.26.2",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import-x": "^4.3.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-perfectionist": "^3.9.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"tailwindcss": "^3.4.14",
"tsx": "^4.19.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
"vite-tsconfig-paths": "^5.0.1"
},
"overrides": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
},
"engines": {
"node": ">=20.0.0"
},
"packageManager": "[email protected]",
"lint-staged": {
"*": "prettier --write --ignore-unknown"
}
}
app/db/index.ts
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { env } from "~/lib/env.server";
import * as schema from "./schema";
export const connection = createClient({
authToken: env.TURSO_AUTH_TOKEN,
url: env.TURSO_DATABASE_URL,
});
export const db = drizzle(connection, { schema });
app/routes/temp._index/route.tsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { db } from "~/db";
export async function loader() {
const users = await db.query.user.findMany();
return json({ users });
}
export default function Route() {
const data = useLoaderData<typeof loader>();
return (
<div>
<h1>Users</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dbCredentials: {
authToken: process.env.TURSO_AUTH_TOKEN || undefined,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
url: process.env.TURSO_DATABASE_URL!,
},
dialect: "turso",
out: "./drizzle/migrations",
schema: "./app/db/schema.ts",
});
I’ve created a reproduction application which is simply the Remix Vercel template with a Turso Drizzle integration following the steps from the Drizzle documentation
This is giving the same error as mentioned before. What is going on here?
https://github.com/petervmeijgaard/remix-drizzle-vercel
I figured out that this is only involving the serverless environment. The Turso connection is working fine on the edge environment:
I've also faced the same issue, and solved the problem by changing the libsql:// scheme to wss:// scheme.
I got the clue from this official turso-drizzle database demo from vercel.
I think it relates to vercel's infrastructure of not handling specific schemes such as libsql://, but I'm not sure since it seems to lack documentation and I didn't dig deep into the problem for right now.
I tried the wss:// idea but quickly ran into issues on Vercel in production with HTTP 429 errors.
Internally, it seems using wss:// results in creating a websocket client, which I'm guessing due to the serverless nature of Vercel is not getting disconnected properly, and resulting in an accumulation and maxing out of available connections.
may this be related?
https://github.com/tursodatabase/libsql/issues/1739