Are there any more elegant ways to use Drizzle ORM in Nest.js besides the providers? For example like in Prisma with PrismaService, all I found is only with providers like:
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
export const PG_CONNECTION = "PG_CONNECTION"; // ignore that it is not separate file
@Module({
providers: [
{
provide: PG_CONNECTION,
inject: [ConfigService],
useFactory: async (config: ConfigService) => {
const connection = postgres(config.get("DATABASE_URL"));
return drizzle(connection, { schema });
},
},
],
exports: [PG_CONNECTION],
})
export class DrizzleModule {}
and then:
import { Inject, Injectable } from '@nestjs/common';
import { PG_CONNECTION } from '../drizzle/drizzle.module';
import { dbType } from 'drizzle-orm/postgres-js'; // ignore, it doesn't matter yet
import * as schema from '../drizzle/schema';
@Injectable()
export class UsersService {
constructor(@Inject(PG_CONNECTION) private drizzle: dbType<typeof schema>) {} // what I'm talking about
async findAll() {
return await this.drizzle.query.users.findMany();
}
}
what I'm talking about, we should inject (provider?) every time, import the constant and type from the db client, instead of this:
import { Inject, Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service'
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return await this.prisma.users.findMany();
}
}
I tried to find a solution but it seems this topic is really not popular.. I was expecting such a big Nest.js and Drizzle ORM community to have a good solution to use Drizzle with Nest
You could create your own wrapper class around the PG_CONNECTION injection token. Something like
@Injectable()
export class DrizzleService {
constructor(@Inject(PG_CONNECTION) readonly db: dbType<typeof schema>) {}
}
And now instead of exporting the PG_CONNECTION token from your DrizzleModule you export the DrizzleService. This would allow you to use the similar private readonly drizzle: DrizzleService as you're able to with prisma and then access this.drizzle.db for your typed database access inside of other providers.
I had a slightly easier approach that didn't involve much. Here is an example of how i did it.
'database.service.ts':
import { Injectable, OnModuleInit } from '@nestjs/common';
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import { db, DatabaseType } from 'drizzle/db';
import {
difficultyEnum,
topicTagEnum,
badgeEnum,
} from 'drizzle/schema/enums/enums';
import * as schema from '../drizzle/schema';
@Injectable()
export class DatabaseService implements OnModuleInit {
private db: DatabaseType;
constructor() {
this.db = db;
}
async onModuleInit() {
try {
const sql = neon(process.env.DB_URL as string);
this.db = drizzle(sql, {
schema,
logger: true,
});
console.log('Database connected successfully');
} catch (error) {
console.error('Failed to connect to the database', error);
throw error;
}
}
getDb() {
return this.db;
}
}
'database.module.ts':
import { Module } from '@nestjs/common';
import { DatabaseService } from './database.service';
@Module({
providers: [DatabaseService],
exports: [DatabaseService], // As we want to share an instance of the 'DatabaseService' between several other modules, we need to export the 'DatabaseService' provider.
})
export class DatabaseModule {}
'app.module.ts':
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { QuestionsModule } from './questions/questions.module';
import { DatabaseModule } from './database/database.module';
@Module({
imports: [
QuestionsModule,
DatabaseModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
'questions.module.ts':
import { Module } from '@nestjs/common';
import { QuestionsService } from './questions.service';
import { QuestionsController } from './questions.controller';
import { DatabaseModule } from 'src/database/database.module';
// This is a feature module
@Module({
imports: [DatabaseModule], // This means 'QuestionsModule' can use any providers (service) that 'DatabaseModule' exports.
controllers: [QuestionsController], // This is the controller that handles incoming HTTP requests related to questions (like fetching, adding, or deleting questions).
providers: [QuestionsService], // This is included as a provider which is responsible for the business logic, such as interacting with the database to manage questions.
})
export class QuestionsModule {}
'questions.service.ts':
import { Injectable } from '@nestjs/common';
import { CreateQuestionDto } from './dto/create-question.dto';
import { UpdateQuestionDto } from './dto/update-question.dto';
import { eq } from 'drizzle-orm';
import { DatabaseService } from 'src/database/database.service';
import {
questionTable,
QuestionTableType,
NewQuestionTableType,
} from '../../drizzle/schema/models/question.model';
import { TopicTagType } from 'drizzle/schema/enums/enums';
@Injectable()
export class QuestionsService {
// This is a default constructor
constructor(private readonly databaseService: DatabaseService) {}
async create(createQuestionDto: CreateQuestionDto) {
return 'This action adds a new question';
}
// GET all questions
async findAllQuestions(): Promise<QuestionTableType[]> {
console.log('question.services.ts findAllQuestions() called'); // debug
try {
const fetchedQuestions = await this.databaseService
.getDb()
.query.questionTable.findMany();
console.log(
`Database Fetch: Retrieved ${fetchedQuestions.length} questions.`,
fetchedQuestions,
); // debug
return fetchedQuestions as QuestionTableType[];
} catch (error) {
console.error('Error fetching questions from database:', error);
throw new Error('Error fetching questions from database');
}
}
async findQuestionById(
questionId: number,
): Promise<QuestionTableType | null> {
console.log('question.services.ts findQuestionById() called'); // debug
try {
const fetchedQuestion: QuestionTableType | undefined =
await this.databaseService.getDb().query.questionTable.findFirst({
where: eq(questionTable.id, questionId),
});
// Guard clause
if (!fetchedQuestion) {
console.error('Question not found in database');
return null;
}
console.log('Database Fetch: Retrieved question:', fetchedQuestion); // debug
return fetchedQuestion;
} catch (error) {
console.error('Error fetching question from database:', error);
throw new Error('Error fetching question from database');
}
}
async findQuestionsByTopic(
topic: TopicTagType,
): Promise<QuestionTableType[]> {
console.log('question.services.ts findQuestionsByTopic() called'); // debug
try {
const fetchedQuestions: QuestionTableType[] = await this.databaseService
.getDb()
.query.questionTable.findMany({
where: eq(questionTable.topicTag, topic),
});
// Guard clause
if (!fetchedQuestions) {
console.error('Questions not found in database');
return null;
}
console.log(
`Database Fetch: Retrieved ${fetchedQuestions.length} questions by topic ${topic}`,
fetchedQuestions,
); // debug
return fetchedQuestions;
} catch (error) {
console.error('Error fetching questions from database:', error);
throw new Error('Error fetching questions from database');
}
}
async update(id: number, updateQuestionDto: UpdateQuestionDto) {
return `This action updates a #${id} question`;
}
async remove(id: number) {
return `This action removes a #${id} question`;
}
}
'questions.controller.ts':
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
NotFoundException,
ValidationPipe,
ParseIntPipe,
ParseFloatPipe,
ParseBoolPipe,
ParseArrayPipe,
ParseUUIDPipe,
ParseEnumPipe,
DefaultValuePipe,
ParseFilePipe,
} from '@nestjs/common';
import { QuestionsService } from './questions.service';
import { CreateQuestionDto } from './dto/create-question.dto';
import { UpdateQuestionDto } from './dto/update-question.dto';
import { topicTagEnum, TopicTagType } from '../../drizzle/schema/enums/enums';
import {
QuestionTableType,
NewQuestionTableType,
} from 'drizzle/schema/models/question.model';
@Controller('questions')
export class QuestionsController {
constructor(private readonly questionsService: QuestionsService) {}
// This is an example method decorator that defines a route handler for POST requests to the specified route.
@Post()
create(@Body() createQuestionDto: CreateQuestionDto) {
return this.questionsService.create(createQuestionDto);
}
@Get()
findAll(): Promise<QuestionTableType[]> {
return this.questionsService.findAllQuestions();
}
@Get(':id')
async findQuestionById(
@Param('id', ParseIntPipe) id: number,
): Promise<QuestionTableType> {
const question = await this.questionsService.findQuestionById(id);
// Guard clause
if (!question) {
throw new NotFoundException(`Question with ID ${id} not found.`);
}
return question;
}
// @Get('topic/:topic')
// findQuestionsByTopic(
// @Param('topic', new ParseEnumPipe(topicTagEnum)) topic: TopicTagType,
// ): Promise<QuestionTableType[]> {
// const questions = this.questionsService.findQuestionsByTopic(topic);
// // Guard clause
// if (!questions) {
// throw new NotFoundException(`Questions with topic ${topic} not found.`);
// }
// return questions;
// }
@Get('topic/:topic')
findQuestionsByTopic(
@Param('topic') topic: TopicTagType,
): Promise<QuestionTableType[]> {
const questions = this.questionsService.findQuestionsByTopic(topic);
// Guard clause
if (!questions) {
throw new NotFoundException(`Questions with topic ${topic} not found.`);
}
return questions;
}
@Patch(':id')
update(
@Param('id') id: string,
@Body() updateQuestionDto: UpdateQuestionDto,
) {
return this.questionsService.update(+id, updateQuestionDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.questionsService.remove(+id);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With