Generating & Instantiating Prisma Client
Every backend application ultimately boils down to one thing: data. Creating it, reading it, updating it, deleting it. In this blog, we’ll master Prisma Client’s CRUD operations and wire them into a fully functional REST API. By the end, you’ll have a Users API that you’d actually be comfortable shipping.
Generating & Instantiating Prisma Client
Setup in 60 Seconds
npx prisma generate
The Singleton Pattern
In Express apps, each import of `PrismaClient` creates a new database connection pool. During development with hot-reload (nodemon), this causes connection exhaustion. The solution: a singleton.
// src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
Pro Tip
CREATE Operations
`create` — Single Record
// src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
`createMany` — Bulk Insert
const result = await prisma.user.createMany({
data: [
{ email: '[email protected]', name: 'Alice' },
{ email: '[email protected]', name: 'Bob' },
],
skipDuplicates: true, // MySQL: skips rows that violate unique constraints
});
// result.count = number of inserted records
READ Operations
`findUnique` — Exact Match (by `@id` or `@unique`)
const user = await prisma.user.findUnique({
where: { email: '[email protected]' },
});
// Returns: User | null
`findFirst` — First Match (any field)
const user = await prisma.user.findFirst({
where: { name: { contains: 'John' } },
orderBy: { createdAt: 'desc' },
});
`findMany` — Multiple Records
const users = await prisma.user.findMany({
where: { active: true },
take: 10,
skip: 0,
orderBy: { createdAt: 'desc' },
});
Performance
`findUnique` is faster than `findFirst` — it generates an indexed lookup. Always prefer `findUnique` when querying by `@id` or `@unique` fields. (prisma.io/docs/reference/api-reference/prisma-client-reference#findunique)
UPDATE Operations
`update` — Single Record
const user = await prisma.user.update({
where: { id: 1 },
data: { name: 'Jane Doe' },
});
`updateMany` — Bulk Update
const result = await prisma.user.updateMany({
where: { active: false },
data: { deletedAt: new Date() },
});
// result.count = number of updated records
`upsert` — Create or Update
One of Prisma’s most powerful methods — insert if not exists, update if it does:
const user = await prisma.user.upsert({
where: { email: '[email protected]' },
update: { name: 'John Updated' },
create: { email: '[email protected]', name: 'John New' },
});
DELETE Operations
// Delete single
await prisma.user.delete({ where: { id: 1 } });
// Delete many
await prisma.user.deleteMany({
where: { createdAt: { lt: new Date('2020-01-01') } }
});
Warning
Selecting Specific Fields
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
id: true,
email: true,
name: true,
// password: omitted — never returned to clients
},
});
Excluding Sensitive Fields (Password, Tokens)
// src/utils/exclude.ts
export function exclude<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
return Object.fromEntries(
Object.entries(obj as any).filter(([k]) => !keys.includes(k as K))
) as Omit<T, K>;
}
// Usage
const fullUser = await prisma.user.findUnique({ where: { id: 1 } });
const safeUser = exclude(fullUser!, ['password', 'resetToken']);
Full CRUD REST API
// src/routes/users.ts
import { PrismaClient } from '../generated/prisma';
import { PrismaMariaDb } from '@prisma/adapter-mariadb';
import dotenv from 'dotenv';
dotenv.config();
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
const adapter = new PrismaMariaDb({
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
connectionLimit: 10,
});
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({ adapter });
if (process.env.NODE_ENV !== 'production') {
// src/routes/users.ts
import { Router, Request, Response, NextFunction } from 'express';
import { prisma } from '../prisma/client';
const router = Router();
// GET /users
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
try {
const users = await prisma.user.findMany({
select: { id: true, email: true, name: true, createdAt: true },
orderBy: { createdAt: 'desc' },
});
res.json({ data: users, count: users.length });
} catch (err) {
next(err);
}
});
// GET /users/:id
router.get('/:id', async (req, res, next) => {
try {
const user = await prisma.user.findUnique({
where: { id: Number(req.params.id) },
select: { id: true, email: true, name: true, createdAt: true },
});
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
} catch (err) {
next(err);
}
});
// POST /users
router.post('/', async (req, res, next) => {
try {
const { email, name } = req.body;
const user = await prisma.user.create({
data: { email, name },
select: { id: true, email: true, name: true },
});
res.status(201).json(user);
} catch (err) {
next(err);
}
});
// PATCH /users/:id
router.patch('/:id', async (req, res, next) => {
try {
const { name } = req.body;
const user = await prisma.user.update({
where: { id: Number(req.params.id) },
data: { name },
select: { id: true, email: true, name: true },
});
res.json(user);
} catch (err) {
next(err);
}
});
// DELETE /users/:id
router.delete('/:id', async (req, res, next) => {
try {
await prisma.user.delete({ where: { id: Number(req.params.id) } });
res.status(204).send();
} catch (err) {
next(err);
}
});
export default router;
// Register the router in `src/index.ts`:
import userRouter from './routes/users';
app.use('/users', userRouter);
Make it work, make it right, make it fast.
Thank You for Spending Your Valuable Time
I truly appreciate you taking the time to read blog. Your valuable time means a lot to me, and I hope you found the content insightful and engaging!
Frequently Asked Questions
`findUnique` only works on `@id` or `@unique` fields and generates a single indexed lookup in MySQL. `findFirst` works on any field using a table scan with `WHERE`. Always prefer `findUnique` for performance when the field supports it.
Not directly — Prisma uses `INSERT IGNORE` under the hood with `skipDuplicates: true` in MySQL. For true upsert-many, loop `upsert()` calls inside a `$transaction` for atomicity.
`const count = await prisma.user.count({ where: { active: true } });` Prisma generates `SELECT COUNT(*) FROM users WHERE active = 1` — fully optimized for MySQL.
Absolutely. Import the singleton `prisma` instance in any Express middleware. Never create new `PrismaClient` instances — always use the singleton from `src/lib/prisma.ts`.
It closes all MySQL connections in the pool. In long-running Express servers, you typically never call it manually. Call it in scripts, seed files, and serverless functions after execution completes.
Comments are closed