Skip to content

Instantly share code, notes, and snippets.

@gamedevsam
Created October 23, 2024 21:17
Show Gist options
  • Save gamedevsam/3930258b947679bc1bdb09d001105a67 to your computer and use it in GitHub Desktop.
Save gamedevsam/3930258b947679bc1bdb09d001105a67 to your computer and use it in GitHub Desktop.
NestJS Prisma server with an extension to log all queries
import { Global, INestApplication, Injectable, Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { ITXClientDenyList } from '@prisma/client/runtime/library';
import { PRISMA_ORM_LOG_LEVEL, PRISMA_SQL_LOG_LEVEL } from '~/server_config';
export type PrismaTransaction = Omit<PrismaClient, ITXClientDenyList>;
const logger = new Logger('prisma');
export const logQueriesExtension = Prisma.defineExtension((client) => {
return client.$extends({
name: 'log_queries',
query: PRISMA_ORM_LOG_LEVEL
? {
$allModels: {
async $allOperations({ operation, model, args, query }) {
const start = performance.now();
const result = await query(args);
const time = performance.now() - start;
logger[PRISMA_ORM_LOG_LEVEL](
`${model}.${operation}(${JSON.stringify(args)}) => ${Array.isArray(result) ? result.length : !!result ? 1 : 0} result(s) in ${time.toFixed(2)}ms`.replaceAll(
/\\?"|"/g,
'',
),
);
return result;
},
},
}
: undefined,
});
});
function extendClient(base: PrismaClient) {
// Add as many as you'd like - no ugly types required!
return base.$extends(logQueriesExtension); //.$extends(findManyAndCountExtension);
}
class UntypedExtendedClient extends PrismaClient {
constructor(options?: ConstructorParameters<typeof PrismaClient>[0]) {
super(options);
if (PRISMA_SQL_LOG_LEVEL) {
// @ts-expect-error: https://www.prisma.io/docs/orm/prisma-client/client-extensions#usage-of-on-and-use-with-extended-clients
this.$on('query', ({ query, params }: Prisma.QueryEvent) => {
let workingQuery = query;
const paramsArray = JSON.parse(params);
for (let i = 0; i < paramsArray.length; ++i) {
workingQuery = workingQuery.replace(
`$${i + 1}`,
`'${typeof paramsArray[i] === 'object' ? JSON.stringify(paramsArray[i]) : paramsArray[i]}'`,
);
}
logger[PRISMA_SQL_LOG_LEVEL](workingQuery);
});
}
return extendClient(this) as this;
}
}
const ExtendedPrismaClient = UntypedExtendedClient as unknown as new (
options?: ConstructorParameters<typeof PrismaClient>[0],
) => PrismaClient & ReturnType<typeof extendClient>;
@Injectable()
export class PrismaService extends ExtendedPrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
super(
PRISMA_SQL_LOG_LEVEL
? {
log: [
{
emit: 'event',
level: 'query',
},
],
}
: undefined,
);
}
async onModuleInit() {
// Uncomment this to establish a connection on startup, this is generally not necessary
// https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
// await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
async enableShutdownHooks(app: INestApplication) {
async function waitForAppClose() {
await app.close();
}
// https://prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-the-beforeexit-hook-from-the-library-engine
process.on('exit', waitForAppClose);
process.on('beforeExit', waitForAppClose);
process.on('SIGINT', waitForAppClose);
process.on('SIGTERM', waitForAppClose);
process.on('SIGUSR2', waitForAppClose);
}
}
@Global()
@Module({
exports: [PrismaService],
providers: [PrismaService],
})
export class PrismaModule {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment