VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
Create the database
Prisma needs two databases: the one your app uses, and a second “shadow” database it uses internally to compute migrations. Create both, and a dedicated user with rights to each:CREATE/DROP privileges on *.* so Prisma can create a shadow database itself, keeps the blast radius of a leaked password to two databases instead of the whole server.
Scaffold the app
latest tag for prisma points at an 8.0 release candidate: fine to try, but the wrong default for a guide meant to keep working.
Initialize Prisma
prisma init runs a postinstall step by default that scaffolds Claude Code, Cursor, and Windsurf agent skills into your repo (.claude/, .windsurf/, .agents/, plus a skills-lock.json). --no-skills skips it. It’s a genuinely useful feature if you’re using an AI coding agent alongside Prisma, just not part of this guide’s scope.
This creates prisma/schema.prisma, a .env file, and prisma7.config.ts (the config filename is version-suffixed in Prisma 7). Set the connection strings:
.env
schema.prisma. As of Prisma 7, connection URLs, including the shadow database URL, live in prisma7.config.ts, not the schema file:
prisma7.config.ts
schema.prisma keeps the generator block prisma init created and now only declares the provider on the datasource, with no connection URL:
prisma/schema.prisma
Run the migration
prisma/migrations/<timestamp>_init/migration.sql, and calls prisma generate for you. Confirm the table exists:
Connect with a driver adapter
Prisma 7’s generated client no longer connects on its own:new PrismaClient() with no arguments now fails a type check. You pass it a driver adapter, one package per database:
@prisma/adapter-mariadb is the adapter for both MariaDB and MySQL: the wire protocol is shared, so it works against VillageSQL the same way it works against stock MySQL. Set up a singleton so Next.js’s dev-mode hot reload doesn’t open a new connection pool on every file save:
src/lib/prisma.ts
The First Connection to a New Account
MySQL 8 and VillageSQL create accounts with thecaching_sha2_password plugin. The first time a given account authenticates, the client has to complete an RSA key exchange; after that the server caches the result and later connections take a faster path that skips it. @prisma/adapter-mariadb refuses that exchange on an unencrypted connection, so on a server that has never authenticated this account, the build fails:
active=0 idle=0 makes it read like the server is unreachable. The underlying driver error is more specific:
.env
asyncmy, and it is intermittent for the same reason: once any client has completed a full authentication for the account, including the mysql CLI, the cached result hides the problem until the cache is cleared by FLUSH PRIVILEGES or a server restart.
The generated client’s import path has two quirks. It has no index.ts, so you import @/generated/prisma/client (the file), not @/generated/prisma (the directory). And every export in that file is typed @ts-nocheck, since it’s generated code you’re meant to import, not read.
Read and write from a Server Component
src/app/page.tsx
prisma.post.findMany() runs as a Server Component, so the list renders with data already in the HTML, with no client-side fetch. The form posts to a Server Action ("use server"), which writes through the same Prisma client and calls revalidatePath("/") so the new post shows up without a client-side refresh.
Running npm run build at this point actually queries the database: because the home page uses no dynamic APIs, Next.js prerenders it as static content at build time, which means findMany() runs during the build, not just at request time. If your database isn’t reachable during npm run build, the build fails there, so a CI pipeline for this app needs a reachable database at build time, not just at runtime.
Frequently Asked Questions
Do I need the shadow database in production?
No.prisma migrate dev uses it to detect schema drift while you’re developing. In production, use prisma migrate deploy, which applies existing migration files without touching a shadow database at all.
Why does my standalone script fail with “Cannot read properties of undefined (reading ‘prepareCacheLength’)”?
process.env.DATABASE_URL is undefined in that script’s environment. Next.js loads .env automatically for next dev and next build, but a plain node or tsx script does not. Add import "dotenv/config" at the top of the script.
Can I use @prisma/adapter-pg’s MySQL equivalent instead?
There isn’t a MySQL-specific adapter name. @prisma/adapter-mariadb is the one Prisma ships for both MariaDB and MySQL, including VillageSQL.
Troubleshooting
See also
- Next.js and Drizzle with MySQL — the same app built on a lighter, SQL-first ORM instead of Prisma
- Next.js with MySQL — connection setup, Server Actions, and Route Handler patterns with no ORM at all
- Using MySQL with ORMs — the settings that matter across every ORM, not just Prisma
- Connection pooling — how a driver adapter’s pool size interacts with your process count

