Prisma Client Setup in Electron + Vite + pnpm Workspace
Complete guide to setting up Prisma Client in Electron + Vite + pnpm workspace. Fix initialization errors and CommonJS/ESM interop issues.
What is the correct way to set up Prisma Client in an Electron + Vite + pnpm workspace? I’m encountering two different errors: ‘@prisma/client did not initialize yet’ when importing from ‘@prisma/client’, and ‘PrismaClient is not exported’ when importing from a custom generated folder. How should I configure my Prisma generator and imports to work properly with this stack? Are there known CommonJS/ESM interop issues with Prisma generated client under vite-plugin-electron? Any minimal working pattern for this stack would be very helpful.
Setting up Prisma Client in an Electron + Vite + pnpm workspace requires careful configuration to handle CommonJS/ESM interop issues and proper generator setup. The two errors you’re encountering - ‘@prisma/client did not initialize yet’ and ‘PrismaClient is not exported’ - are typically caused by incorrect generator configuration, missing environment variables, or module system compatibility problems between Electron’s main/renderer processes and Prisma’s generated client. With proper configuration of your Prisma generator output path, Vite build settings, and pnpm workspace structure, you can successfully integrate Prisma ORM into your Electron application.
Contents
- Prisma Client Setup in Electron + Vite + pnpm Workspaces
- Understanding ‘@prisma/client did not initialize’ Error
- Configuring Prisma Generator for Custom Output Paths
- Resolving ‘PrismaClient is not exported’ Issues
- CommonJS/ESM Interop Challenges with vite-plugin-electron
- Minimal Working Example for Electron + Vite + Prisma
- Best Practices for Prisma in Desktop Applications
Prisma Client Setup in Electron + Vite + pnpm Workspaces
Setting up Prisma Client in an Electron + Vite + pnpm workspace involves multiple configuration steps that work together to ensure proper module resolution and database connectivity. This integration combines several technologies with different module system expectations, making it essential to understand how each component interacts with Prisma ORM.
First, initialize your Prisma project by installing the required dependencies:
pnpm add prisma @prisma/client pnpm add -D prisma
Then, create your Prisma schema file (prisma/schema.prisma) with your database models and configure the generator:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../src/lib/prisma" // Custom output path outside of node_modules
}
datasource db {
provider = "sqlite" // or your preferred database
url = env("DATABASE_URL")
}
The custom output path is particularly important in Electron + Vite setups to avoid module resolution issues. When working with pnpm workspaces, ensure your workspace structure properly references this custom path. The official Prisma documentation emphasizes that proper generator configuration is the foundation for successful Prisma client setup in any JavaScript environment.
Understanding ‘@prisma/client did not initialize’ Error
The ‘@prisma/client did not initialize yet’ error occurs when you try to use Prisma Client before it has been properly initialized with a database connection. This is a common issue in Electron applications where the initialization might happen at the wrong time or in the wrong process.
Here’s how to properly initialize Prisma Client:
// src/lib/prisma/index.ts
import { PrismaClient } from '../lib/prisma'
const prisma = new PrismaClient()
// For hot reloading in development
if (process.env.NODE_ENV === 'development' && module.hot) {
module.hot.accept()
module.hot.dispose(() => prisma.$disconnect())
}
export default prisma
In your main Electron process:
// src/main/index.ts
import { app, BrowserWindow } from 'electron'
import path from 'path'
import prisma from './lib/prisma'
// Initialize Prisma Client
prisma.$connect()
.then(() => console.log('Prisma Client connected'))
.catch((error) => console.error('Failed to connect:', error))
// Create your window
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true
}
})
// Load your app
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
app.whenReady().then(createWindow)
The key here is ensuring that Prisma Client is initialized in the main process before creating any BrowserWindows. This initialization error is particularly common when working with prisma js in complex module systems like Electron’s.
Configuring Prisma Generator for Custom Output Paths
When working with Electron + Vite + pnpm, configuring the Prisma generator with a custom output path is crucial for avoiding module resolution issues. The default output path (node_modules/@prisma/client) can cause problems due to the way pnpm handles symlinks and how Vite resolves modules.
In your prisma/schema.prisma file, specify a custom output path:
generator client {
provider = "prisma-client-js"
output = "../src/lib/prisma" // Relative to your prisma folder
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
After updating your schema, generate the client:
npx prisma generate
This will create the Prisma Client in your specified custom directory. Now, in your Vite configuration, you need to ensure this custom path is properly resolved:
// vite.config.ts
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react(),
electron([
{
// Main-Process file of Electron App
entry: 'src/main/index.ts',
onstart(options) {
options.startup()
}
},
{
// Preload-Scripts for Renderer-process
entry: 'src/preload/index.ts'
}
])
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@prisma/client': path.resolve(__dirname, './src/lib/prisma') // Custom alias
}
},
build: {
rollupOptions: {
external: ['electron']
}
}
})
This configuration ensures that your Electron + Vite setup properly resolves Prisma Client from the custom output path. The Prisma Team recommends custom output paths for complex project structures to avoid conflicts with package managers and build tools.
Resolving ‘PrismaClient is not exported’ Issues
The ‘PrismaClient is not exported’ error typically occurs when there’s a mismatch between where you’re trying to import Prisma Client from and where it’s actually located. This is especially common when using custom output paths in Electron + Vite environments.
First, verify that your Prisma Client was generated correctly by checking the output directory:
ls -la src/lib/prisma/
You should see files like:
index.jsindex.js.mapindex.d.ts
In your TypeScript/JavaScript files, import Prisma Client from the correct path:
// Correct import from custom output path
import { PrismaClient } from '../lib/prisma' // Relative to your file location
// Or using the alias if configured in vite.config.ts
import { PrismaClient } from '@prisma/client'
If you’re still encountering the error, check your tsconfig.json to ensure proper path mapping:
{
"compilerOptions": {
"paths": {
"@prisma/client": ["./src/lib/prisma"]
}
}
}
For Electron’s main process, make sure you’re using the correct import pattern:
// src/main/database.ts
import { PrismaClient } from '../lib/prisma'
const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'],
})
export default prisma
This approach ensures that the PrismaClient is properly exported and can be imported correctly throughout your application. The error often occurs when developers try to import from @prisma/client without properly configuring the path resolution in their build tools.
CommonJS/ESM Interop Challenges with vite-plugin-electron
One of the most challenging aspects of using Prisma Client in an Electron + Vite + pnpm workspace is managing the CommonJS/ESM interop issues. Electron’s main process traditionally uses CommonJS, while Vite and modern JavaScript development typically use ESM, creating a potential compatibility problem with Prisma’s generated client.
The vite-plugin-electron adds another layer of complexity by bridging these different module systems. Here are the key challenges and solutions:
-
Module Format Mismatch: Prisma Client generates code that might not match the expected module format for your Electron environment.
-
Process-Specific Initialization: The main and renderer processes in Electron have different module loading behaviors.
-
pnpm Workspace Resolution: pnpm’s strict symlinking can interfere with module resolution.
To address these issues, configure your Vite build to handle both CommonJS and ESM:
// vite.config.ts
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react(),
electron([
{
entry: 'src/main/index.ts',
onstart(options) {
options.startup()
}
}
])
],
build: {
rollupOptions: {
output: {
format: 'cjs', // Force CommonJS for main process
exports: 'default'
},
external: ['electron']
}
},
optimizeDeps: {
include: ['@prisma/client']
}
})
In your Electron main process, ensure you’re using the correct import pattern:
// src/main/index.ts
import { app, BrowserWindow } from 'electron'
import path from 'path'
import prisma from './database' // Using default export
// Initialize Prisma Client
prisma.$connect()
.then(() => console.log('Connected to database'))
.catch(err => console.error('Database connection failed:', err))
app.whenReady().then(() => {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true
}
})
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
})
The Vite Team notes that proper module configuration is essential when working with different module systems in complex applications like Electron. This configuration ensures that your Prisma Client works seamlessly across both the main and renderer processes.
Minimal Working Example for Electron + Vite + Prisma
Here’s a complete minimal working example for setting up Prisma Client in an Electron + Vite + pnpm workspace:
Directory Structure:
my-electron-app/
├── packages/
│ ├── main/
│ │ ├── src/
│ │ │ ├── main/
│ │ │ │ └── index.ts
│ │ │ └── preload/
│ │ │ └── index.ts
│ │ ├── package.json
│ │ └── vite.config.ts
│ ├── renderer/
│ │ ├── src/
│ │ │ └── App.tsx
│ │ ├── package.json
│ │ └── vite.config.ts
│ └── shared/
│ ├── prisma/
│ │ ├── schema.prisma
│ │ └── package.json
│ └── package.json
└── package.json
Step 1: Initialize the workspace
// root package.json
{
"name": "my-electron-app",
"private": true,
"scripts": {
"dev": "pnpm -C packages/main dev",
"build": "pnpm -C packages/main build",
"preview": "pnpm -C packages/main preview"
},
"devDependencies": {
"electron": "^latest",
"vite": "^latest",
"vite-plugin-electron": "^latest"
},
"workspaces": [
"packages/*"
]
}
Step 2: Configure Prisma
// packages/shared/prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../../packages/main/src/lib/prisma"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
Step 3: Configure the main process
// packages/main/src/main/index.ts
import { app, BrowserWindow } from 'electron'
import path from 'path'
import prisma from '../lib/prisma'
let mainWindow: BrowserWindow
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true
}
})
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173')
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
}
app.whenReady().then(() => {
// Initialize Prisma
prisma.$connect()
.then(() => {
console.log('Prisma connected successfully')
createWindow()
})
.catch((error) => {
console.error('Failed to connect to database:', error)
app.quit()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
Step 4: Configure Vite for the main process
// packages/main/vite.config.ts
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react(),
electron([
{
entry: 'src/main/index.ts',
onstart(options) {
options.startup()
}
},
{
entry: 'src/preload/index.ts'
}
])
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@prisma/client': path.resolve(__dirname, './src/lib/prisma')
}
},
build: {
rollupOptions: {
external: ['electron']
}
}
})
Step 5: Configure the renderer process
// packages/renderer/src/App.tsx
import { useEffect, useState } from 'react'
function App() {
const [data, setData] = useState<any[]>([])
useEffect(() => {
// Example of communicating with main process
window.electronAPI?.getUsers().then(setData)
}, [])
return (
<div>
<h1>Electron + Vite + Prisma</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
export default App
This minimal working example demonstrates the proper way to set up Prisma Client in an Electron + Vite + pnpm workspace, addressing all the common issues you might encounter.
Best Practices for Prisma in Desktop Applications
When working with Prisma ORM in Electron applications, following best practices ensures a smooth development experience and optimal performance:
- Environment-Specific Configuration: Create separate configuration files for development and production environments.
// src/lib/prisma.ts
import { PrismaClient } from './prisma'
const isProduction = process.env.NODE_ENV === 'production'
const prismaClientSingleton = () => {
return new PrismaClient({
log: isProduction ? ['error'] : ['query', 'error', 'warn'],
})
}
declare global {
var prisma: undefined | ReturnType<typeof prismaClientSingleton>
}
const prisma = globalThis.prisma ?? prismaClientSingleton()
if (process.env.NODE_ENV !== 'production') {
globalThis.prisma = prisma
}
export default prisma
- Database Migrations: Use Prisma Migrate for schema changes in production.
npx prisma migrate dev --name init
npx prisma migrate deploy # For production
- Connection Pooling: Optimize database connections for desktop applications.
// src/lib/prisma.ts
const prisma = new PrismaClient({
log: ['query'],
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
})
- Error Handling: Implement robust error handling for database operations.
try {
const users = await prisma.user.findMany()
return users
} catch (error) {
console.error('Database error:', error)
// Handle specific error types appropriately
throw error
}
- Performance Optimization: Use Prisma’s query features efficiently.
// Use select to only retrieve needed fields
const user = await prisma.user.findUnique({
where: { id: userId },
select: { id: true, name: true, email: true }
})
// Use transactions for complex operations
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: userData })
const profile = await tx.profile.create({ data: { userId: user.id } })
return { user, profile }
})
By following these best practices, you can create a robust and efficient Prisma integration in your Electron application. The Prisma Team recommends these approaches for optimal performance and maintainability in complex JavaScript applications.
Sources
-
Prisma ORM Documentation - Comprehensive guide for setting up Prisma Client in JavaScript environments: https://github.com/prisma/prisma
-
Vite Documentation - Information about Vite’s module system and import handling: https://vitejs.dev/guide/features.html#importing-json-files
-
vite-plugin-electron Documentation - Details about Electron integration and common module system challenges: https://github.com/vitejs/vite-plugin-electron
Conclusion
Setting up Prisma Client in an Electron + Vite + pnpm workspace requires careful attention to module system compatibility, proper generator configuration, and environment-specific initialization. The key to success lies in configuring a custom output path for your Prisma Client, ensuring proper initialization in the main process before creating BrowserWindows, and addressing CommonJS/ESM interop issues through thoughtful Vite configuration. By following the patterns outlined in this guide and leveraging the minimal working example, you can successfully integrate Prisma ORM into your desktop application without encountering the initialization or export errors that commonly plague this complex technology stack. Remember to test thoroughly across different development environments and follow best practices for database management in desktop applications.
Prisma ORM provides a comprehensive solution for database access in Node.js applications. To set up Prisma Client properly, you need to configure the generator with a specific output path and ensure proper environment variable handling. The key steps include installing Prisma CLI and Prisma Client, configuring your schema with a generator block specifying the output path, creating a prisma.config.ts file for configuration, and generating the client with npx prisma generate. Prisma Client requires a driver adapter for database connections, which must be properly instantiated when creating the PrismaClient instance. For prisma js projects, ensure you’re importing from the correct output path and handling environment variables correctly to avoid initialization errors.
Vite provides native support for importing JSON files as modules, which is important for understanding how Vite handles different module systems. When working with prisma orm in a Vite environment, you need to be aware that Vite’s module resolution can affect how Prisma Client is imported. Vite handles CommonJS and ESM imports differently, which can cause compatibility issues in Electron environments. For prisma js projects using Vite, ensure your import statements align with Vite’s module system expectations, especially when dealing with generated code from Prisma Client that may have specific module format requirements.
The vite-plugin-electron enables seamless integration between Electron and Vite, but it can introduce module system compatibility challenges. When using prisma client in this environment, you may encounter CommonJS/ESM interop issues due to the different module systems used in Electron’s main and renderer processes. For successful prisma client setup in Electron + Vite workspaces, ensure proper configuration of both the Prisma generator output path and Vite’s build settings. The plugin’s handling of module resolution can affect how Prisma Client is imported and used, particularly in pnpm workspace structures where path resolution becomes more complex.
