এর আগে NestJS এ সূচনা লেখায় নেস্ট সম্পর্কে বিস্তারিত জেনেছি। দেখেছি কিভাবে একটা নেস্ট অ্যাপ ব্যবহার করে বিভিন্ন API রাউট তৈরি করা যায়। এবার নেস্ট ব্যবহার করে সিম্পল CRUD অ্যাপ তৈরি করব। যেখানে ডেটাবেজ হিসেবে SQLite ব্যবহার করব। নেস্টে অন্যান্য ডেটাবেজও সাপোর্ট করে। SQLite ব্যবহার করব লেখা সিম্পল রাখার জন্য।
প্রথমে একটা প্রজেক্ট তৈরি করে নিব।
nest new project-name
আমরা ORM হিসেবে প্রিজমা ব্যবহার করব। নেস্ট প্রিজমা ন্যাটিভলি সাপোর্ট করে।
npm install prisma --save-dev
npm install @prisma/client
npx prisma init --datasource-provider sqlite
উপরের কমান্ড prisma/schema.prisma এবং .env ফাইল তৈরি করে দিবে। আমরা একটা নোট অ্যাপ তৈরি করব। তার জন্য এভাবে স্কিমা তৈরি করব। prisma/schema.prisma:
generator client {
  provider = "prisma-client-js"
}
datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}
model Note {
  id        Int      @id @default(autoincrement())
  title     String
  content   String
  createdAt DateTime @default(now())
}
.env ফাইলে ডেটাবেজ URL যোগ করবঃ
DATABASE_URL="file:./dev.db"
এরপর ডেটাবেজ তৈরি এবং প্রিজমা ক্লায়েন্ট তৈরি করবঃ
npx prisma migrate dev --name init
npx prisma generate
যা প্রিজমা ক্লায়েন্ট এবং dev.db ফাইল তৈরি করে দিবে।
prisma.config.ts ফাইলে dotenv কনফিগ  ইম্পোর্ট না করলে Failed to load config file../note" as a TypeScript/JavaScript module. Error: PrismaConfigEnvError: Missing required environment variable: DATABASE_URL এরর দেখাতে পারে। তাই   import “dotenv/config”;  যোগ করে দিব prisma.config.ts ফাইলে:
import { defineConfig, env } from "prisma/config";
import "dotenv/config";
export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  engine: "classic",
  datasource: {
    url: env("DATABASE_URL"),
  },
});
এবার প্রিজমা সার্ভিস তৈরি করব prisma/prisma.service.ts:
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }
  async onModuleDestroy() {
    await this.$disconnect();
  }
}
এবং প্রিজমা মডিউল তৈরি করব prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
ডেটাবেজ কনফিগারেশন শেষ।
এবার নোটের জন্য মডিউল, কন্ট্রোলার এবং সার্ভিস জেনারেট করব। তার জন্য এভাবে কমান্ড লিখতে পারিঃ
nest g module notes
nest g controller notes
nest g service notes
অথবা সব কিছু এক সাথে জেনারেট করার জন্যঃ
nest g resource notes
যা আমাদের জিজ্ঞেস করবে কিসের জন্য এই রিসোর্স তৈরি করব। আমরা সিলেক্ট করব REST API। এরপর জিজ্ঞেস করব CRUD এন্ট্রি পয়েন্ট তৈরি করবে কিনা, y প্রেস করলে আমাদের রিসোর্স ফাইল তৈরি করে দিবে। যেখানে থাকবেঃ
- notes.module.ts
- notes.controller.ts
- notes.service.ts
- dto/create-note.dto.ts, dto/update-note.dto.ts (রিকোয়েস্ট ভ্যালিডেশনের জন্য)
- entities/note.entity.ts
সব কিছু সব কিছুর সাথে কানেক্টেড থাকবে। এখন প্রজেক্ট রান করে দেখতে পারবঃ
npm run start:dev
এবার http://localhost:3000/notes ভিজিট করলে দেখতে পাবো "This action returns all notes"
এর কারণ নোট কন্ট্রোলার এবং নোট সার্ভিস প্রিজমার সাথে এখনো কানেক্ট হয়নি। এখন হার্ড কোডেড স্ট্রিং রিটার্ণ করছে। আমরা প্রিজমার সাথে কানেক্ট করব। তার জন্য src/notes/notes.service.ts: এডিট করে নিচের মত করে লিখবঃ
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';  
import { CreateNoteDto } from './dto/create-note.dto';
import { UpdateNoteDto } from './dto/update-note.dto';
@Injectable()
export class NotesService {
  constructor(private prisma: PrismaService) {}
  create(data: CreateNoteDto) {
    return this.prisma.note.create({ data });
  }
  findAll() {
    return this.prisma.note.findMany({
      orderBy: { createdAt: 'desc' },
    });
  }
  async findOne(id: number) {
    const note = await this.prisma.note.findUnique({ where: { id } });
    if (!note) throw new NotFoundException('Note not found');
    return note;
  }
  async update(id: number, data: UpdateNoteDto) {
    await this.findOne(id); // to ensure existence
    return this.prisma.note.update({ where: { id }, data });
  }
  async remove(id: number) {
    await this.findOne(id);
    return this.prisma.note.delete({ where: { id } });
  }
}
এবং নোট কন্ট্রোলার src/notes/notes.controller.ts এডিট করে নিচের মত করে লিখবঃ
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { NotesService } from './notes.service';
import { CreateNoteDto } from './dto/create-note.dto';
import { UpdateNoteDto } from './dto/update-note.dto';
@Controller('notes')
export class NotesController {
  constructor(private readonly notesService: NotesService) {}
  @Post()
  create(@Body() createNoteDto: CreateNoteDto) {
    return this.notesService.create(createNoteDto);
  }
  @Get()
  findAll() {
    return this.notesService.findAll();
  }
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.notesService.findOne(+id);
  }
  @Patch(':id')
  update(@Param('id') id: string, @Body() updateNoteDto: UpdateNoteDto) {
    return this.notesService.update(+id, updateNoteDto);
  }
  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.notesService.remove(+id);
  }
}
এবার প্রিজমা মডিউল অ্যাপ মডিউলের সাথে কানেক্ট করতে হবে src/app.module.ts:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { NotesModule } from './notes/notes.module';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
  imports: [NotesModule, PrismaModule],
  controllers: [AppController],
  providers: [AppService, ],
})
export class AppModule {}
dto ফাইলের কাজ হচ্ছে ডেটা ভেলিডেশন।
src/notes/dto/create-note.dto.ts
import { IsString } from 'class-validator';
export class CreateNoteDto {
  @IsString()
  title: string;
  @IsString()
  content: string;
}
এর জন্য ভ্যালিডটর ইন্সটল করে নিতে হবেঃ
npm install class-validator class-transformer
এবং ফানালি অ্যাপ রান করে দেখতে পারবঃ
npm run start:dev
ভ্যালিডেশন ঠিক মত কাজ করার জন্য main.ts এ ভ্যালিডেটর পাইপলাইন যোগ করতে হবেঃ
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
  }));
  await app.listen(3000);
}
bootstrap();
এই নোটের URL হচ্ছে http://localhost:3000/notes।এই URL এ গেলে সব গুলো নোটের JSON ডেটা রিটার্ণ করবে। যেহেতু কোন নোট নেই টেবিলে, তাই একটা শূন্য অ্যারে রিটার্ণ করবে। যে কোন API এন্ডপয়েন্ট টেস্ট করার অ্যাপ ব্যবহার করে টেস্ট করতে পারেন। কমান্ডলাইন থেকে সরাসরি নোট যোগ করতে নিচের মত কমান্ড লিখতে পারেনঃ
curl -X POST http://localhost:3000/notes \
  -H "Content-Type: application/json" \
  -d '{"title":"First Note","content":"This is my first note"}'
একটা সিঙ্গেল নোট দেখা যাবে /notes/id ব্যবহার করে। যেমনঃ http://localhost:3000/notes/1
curl http://localhost:3000/notes/1
আপডেট করতে চাইলেঃ
curl -X PATCH http://localhost:3000/notes/1 \
  -H "Content-Type: application/json" \
  -d '{"content":"Updated content"}'
ডিলেট করতে চাইলেঃ
curl -X DELETE http://localhost:3000/notes/1
মাইগ্রেশন
এখন আমরা চাইলে খুব সহজে নতুন ফিল্ড যোগ করতে পারব। তার জন্য prisma/schema.prisma এডিট করে নোটে নতুন একটা ফিল্ড যোগ করব। যেমন ক্যাটেগরিঃ
model Note {
  id        Int      @id @default(autoincrement())
  title     String
  content   String
  category  String?   // NEW FIELD, optional
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
যা একটা অপশনাল ফিল্ড।
এবার নতুন ফিল্ড ব্যবহার করে ডেটাবেজ মাইগ্রেট করে নিবঃ
npx prisma migrate dev --name add_category_to_note
মাইগ্রেশনের পর প্রিজমা ক্লায়েন্ট রিজেনারেট করে নিবঃ
npx prisma generate
নতুন ফিল্ড ভ্যালিডেট করার জন্য src/notes/dto/create-note.dto.ts এডিট করবঃ
import { IsString, IsOptional } from 'class-validator';
export class CreateNoteDto {
  @IsString()
  title: string;
  @IsString()
  content: string;
  @IsOptional()
  @IsString()
  category?: string;   // new field
}
এবার আমরা নোটে ক্যাটেগরিও যোগ করতে পারবঃ
curl -X POST http://localhost:3000/notes \
  -H "Content-Type: application/json" \
  -d '{"title":"New Note","content":"Hello","category":"Work"}'