Add Email and Password Auth to libSQL with Better Auth
Short version: lbase create auth --engine libsql --start gives you a libSQL server on an HTTP endpoint, Better Auth talks to it through its Kysely dialect, and email and password sign-in is a few lines of configuration on top. Passwords are hashed with scrypt and land in the account table with providerId set to credential, so you never write a hashing line. Going to production is two environment variables, because it is the same engine on both sides.
Most "auth as a service" products hand you a hosted black box. The other option is a blank database and a weekend of wiring. libSQL plus Better Auth lands in the middle: a real SQLite-compatible database you own, with email and password sign-in working in minutes.
This post builds an auth server on libSQL locally with the Layerbase CLI (formerly SpinDB), then deploys the exact same setup to Layerbase Cloud, where the database is set up for the Better Auth schema and applies it in one click.
What is the Layerbase CLI? It is a single CLI for running any database engine locally, no Docker required.
Run libSQL locally
npm i -g layerbase
lbase create auth --engine libsql --startThat gives you a libSQL server with an HTTP endpoint. Grab its connection string:
lbase url authhttp://127.0.0.1:8080Wire up Better Auth
Better Auth talks to libSQL through its Kysely dialect. Install the pieces:
pnpm add better-auth @libsql/client @libsql/kysely-libsql kysely// auth.ts
import { betterAuth } from 'better-auth'
import { createClient } from '@libsql/client'
import { LibsqlDialect } from '@libsql/kysely-libsql'
export const auth = betterAuth({
database: {
dialect: new LibsqlDialect({
client: createClient({
url: process.env.LIBSQL_URL!,
authToken: process.env.LIBSQL_AUTH_TOKEN,
}),
}),
type: 'sqlite',
},
emailAndPassword: { enabled: true },
})Better Auth needs four tables (user, session, account, verification). Generate and apply them with its CLI:
npx @better-auth/cli migrateThat is the only migration step, and on Layerbase Cloud it is a single click (more on that below).
Sign up and sign in
The client SDK reads like Supabase's, so if you have used auth.signUp and signInWithPassword before, this will feel familiar:
// client.ts
import { createAuthClient } from 'better-auth/client'
export const authClient = createAuthClient()
await authClient.signUp.email({
email: 'ada@example.com',
password: 'a-strong-password',
name: 'Ada Lovelace',
})
await authClient.signIn.email({
email: 'ada@example.com',
password: 'a-strong-password',
})Passwords are hashed with scrypt and stored in the account table with providerId set to credential. Nothing lands in plaintext, and you never wrote a hashing line.
Go to production without changing engines
Your local libSQL is bit for bit the same engine you run in production. On Layerbase Cloud, the create flow has a Set up authentication wizard that scaffolds exactly this. Pick your sign-in methods (email and password, plus optional Google or GitHub), and it provisions a database set up for the Better Auth schema, which you apply in one click from the dashboard (or run your own migration). libSQL is the default engine; Postgres is a click away if you would rather. You can seed an admin user when you apply the schema and sign in with it immediately.
Swap two environment variables and ship:
# .env
LIBSQL_URL=libsql://your-db.cloud.layerbase.dev
LIBSQL_AUTH_TOKEN=your-tokenSame auth.ts, same client calls, real TLS connection. If you would rather use Auth.js (Drizzle, Kysely, or Prisma) instead of Better Auth, the dashboard ships copy-paste recipes for each.
Once it is running, the Auth console in the dashboard manages the users for you: list accounts, reset a password, ban someone, or delete them, without writing SQL against the user table. It recognizes Better Auth, Supabase GoTrue, Payload, and generic schemas across libSQL, Postgres, MySQL, and MariaDB, so if you migrate an existing auth database in (a Supabase project, say, with its password hashes intact) you manage those users from the same place.
Not just Next.js
Better Auth is framework agnostic, so the same config works behind Express, Hono, SvelteKit, or plain Node. And because libSQL speaks the SQLite protocol, you can connect from Python (libsql-client plus passlib) or hand-roll sessions with the raw @libsql/client if you want zero dependencies.
FAQ
Do I have to write password hashing myself?
No, and you should not want to. Better Auth hashes with scrypt and stores the result in the account table with providerId set to credential. Nothing is written in plaintext and there is no hashing code in your project to get wrong later.
Does this work outside Next.js?
Better Auth is framework agnostic, so the same configuration sits behind Express, Hono, SvelteKit, or plain Node. From other languages, libSQL speaks the SQLite protocol, so Python can use libsql-client with passlib, and you can hand-roll sessions on the raw @libsql/client if you want no dependencies at all.
What changes when I deploy?
Two environment variables. The local libSQL and the hosted one are the same engine, so auth.ts and your client calls are untouched; you point LIBSQL_URL and LIBSQL_AUTH_TOKEN at the managed database and get TLS on the connection.
Can I use Postgres instead of libSQL?
Yes, it is a click in the create flow. libSQL is the default because it is the lightest thing that behaves like a real database in both places, but the Better Auth schema and the wizard work the same against Postgres.
How do I manage users once people are signing up?
Through the Auth console in the dashboard rather than by writing SQL against the user table: list accounts, reset a password, ban someone, delete them. It recognizes Better Auth, Supabase GoTrue, Payload, and generic schemas across libSQL, Postgres, MySQL, and MariaDB, so an auth database migrated in from elsewhere, with its password hashes intact, is managed from the same place.
Layerbase CLI command reference
lbase create auth --engine libsql --start # create + start a local libSQL
lbase url auth # print the connection string
lbase connect auth # open a shell
lbase stop auth # stop when you are done
lbase start auth # bring it backThe Layerbase CLI runs 21 engines the same way, so the Valkey cache and Postgres database your app also needs are one command each. When you are ready for production, Valkey and Postgres are a click away on Layerbase Cloud. Prefer a desktop app? Grab Layerbase Desktop.
Keep reading
- libSQL vs TursolibSQL is the open source SQLite fork. Turso is the company behind it, its managed platform, and now a separate Rust rewrite with the same name. Here is how the three fit together in September 2026.
- Your SQLite File Deserves a URLA SQLite file stops working the moment something other than one process needs it. Here is how to put the same file behind an address, with a Postgres driver or the libSQL client, without rewriting a schema.
- Cloudflare D1 alternatives: what you are actually coupled toD1 is SQLite, so your SQL is portable. The coupling is somewhere else: there is no wire protocol, the SQL export rounds any integer past 2^53, and you cannot get the file underneath. Here is an honest inventory, the alternatives, and when staying on D1 is the right call.
- Migrating from Turso to LayerbaseTurso meters reads and writes per row, which gets unpredictable on real workloads. Here is how to move your libSQL database to flat-priced managed hosting on Layerbase: dump it, load it, and point the same libSQL client at a new URL.