# ใช้ NestJS ต่อ Database อย่างไร?
#Dev#Javascript#Nestjs#Backend#Database#ORM#Libraryเหมือนจะยากแต่ก็ไม่ยากอย่างที่คิด ในการต่อ Database นั้น ทางเลือกหลักๆที่เรามีก็ คือ Query เอาเอง หรือในบทความนี้ที่ผมจะมาใช้ TypeORM กันครับ
เจ้าตัว TypeORM เป็น Library สำหรับทำ ORM (Object Relational Mapping) ข้อดีสั้นๆก็คือเราไม่ต้องเขียน Query เองเลย
เราสามารถ SELECT, INSERT, DELETE, UPDATE ได้โดย Map Entity (Class แสดงหน้าตาข้อมูล) กับ Database
# ติดตั้ง
# สร้างโปรเจค
ผ่านคำสั่ง
nest new sample-orm
# ติดตั้ง Library ที่จะใช้
หลักๆก็จะมี
- @nestjs/typeorm
- typeorm
- sqlite3 (ใช้ตัวนี้เพราะขี้เกียจติดตั้ง MySQL 🤣)
npm install @nestjs/typeorm typeorm sqlite3
ห้องต้องการต่อกับ Database ตัวอื่นก็แค่เปลี่ยน sqlite3 ซะ เช่น mysql, postgresql เป็นต้น
# ติดตั้งโปรแกรมอ่าน Database
ในที่นี้ผมจะใช้ SQLite Studio ครับ Download (opens new window)
ใช้เพื่อดูฐานข้อมูล SQLite ใครมีตัวเลือกสะดวกกว่านี้ก็ใช้ได้ครับ
# ติดตั้ง Postman
Postman นั้นเป็นอีกหนึ่งในโปรแกรมยอดนิยมเลยครับใช้ในการทดสอบ Request ได้อย่างง่ายๆ Download (opens new window)
หากใช้ไม่เป็น อาจจะลองอ่านจาก ที่นี่ (opens new window)
# มาเริ่มโค้ดกันเลย
สิ่งที่เราจะทำหลักๆเลยมีประมาณนี้ครับ
- ตั้งค่า TypeORM
- สร้าง Module ใหม่
- สร้าง Entity
- ประกอบร่าง
- ลองเขียน Service แล้วเรียกดู
# ตั้งค่า TypeORM กันก่อน
ง่ายๆเลยแค่เพิ่ม TypeOrmModule เข้าไปที่
src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: './app.sqlite',
entities: [],
synchronize: process.env.NODE_ENV != 'production',
}),
],
})
export class AppModule {}
เนื่องจาก app.controller.ts
และ app.service.ts
ไม่ได้ใช้ ผมจึงทำการลบออกไปเลยครับ
จากโค้ดด้านบนเราได้ทำการตั้งค่า TypeORM ให้เชื่อมต่อ Database SQLite โดยที่ไฟล์ Database ของเราจะเป็นไฟล์ app.sqlite
ในส่วนของ entities นั้น เมื่อเราสร้างเสร็จแล้วเราจะเอามาใส่ครับ
สุดท้าย synchronize เราใส่เพื่อให้มันปรับ Table ใน Database ตาม Entity ของเรา (ด้วยความขี้เกียจ)
ข้อควรระวังในการใช้ synchronize ก็คือ อย่าใช้ใน Production หรือโค้ดบนระบบจริง อาจจะทำให้ข้อมูลเสียหายได้
# Module ใหม่
ผมจะสร้าง Module ใหม่มาจัดการข้อมูล Album ของผมกันครับ
nest g module albums
nest g controller albums
nest g service albums
ไฟล์ *.spec.ts
ลบทิ้งไปก่อนก็ได้ครับ เนื่องจากเราจะยังไม่ทำ Testing กันในบทความนี้
# สร้าง Entity
ทดลองสร้างแบบยังไม่ได้กำหนดว่าจะใช้กับ TypeORM จะได้แบบนี้ครับ
src/albums/entity/album.entity.ts
export class Album {
id: number;
title: string;
remark: string;
}
และถ้าเราจะใช้กับ TypeORM เราก็แค่เพิ่ม
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Album {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 30 })
title: string;
@Column({ length: 255 })
remark: string;
}
@Entity เป็นการกำหนดว่า class นี้จะเป็น TypeORM Entity
@PrimaryGeneratedColumn กำหนดให้ Field หรือ Column นี้เป็น Primary และมี Auto Increment (ถ้าไม่กำหนดค่าตอน Insert มันจะเพิ่มค่าให้เอง)
@Column กำหนดให้เป็น Field หรือ Column ทั่วๆไป (โดยเราจะกำหนดค่า default, length, nullable ได้ด้วยถ้าต้องการ)
# เอา Entity ไปใช้
เมื่อเราได้ Entity มาแล้วเราจะเอากลับไปใส่ที่ src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AlbumsModule } from './albums/albums.module';
import { Album } from './albums/entity/album.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: './app.sqlite',
entities: [Album],
synchronize: process.env.NODE_ENV != 'production',
}),
AlbumsModule,
],
})
export class AppModule {}
และที่ไหนที่ต้องการใช้ Entity นี้ (ในที่นี้คือที่ AlbumModule) ก็ต้องเพิ่มตามนี้ครับ
src/albums/albums.module.ts
import { Module } from '@nestjs/common';
import { AlbumsController } from './albums.controller';
import { AlbumsService } from './albums.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Album } from './entity/album.entity';
@Module({
imports: [TypeOrmModule.forFeature([Album])],
controllers: [AlbumsController],
providers: [AlbumsService],
})
export class AlbumsModule {}
# เขียนส่วนของ Service
ตัว TypeOrmModule จะ Inject Repository มาให้เราใช้ด้วยครับ (Repository คือตัวกลางในการติดต่อกับ Datasource ซึ่งในที่นี้ก็คือ Database ของเรา)
เราสามารถทำตามนี้ได้เลย
src/albums/album.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Album } from './entity/album.entity';
@Injectable()
export class AlbumsService {
constructor(
@InjectRepository(Album)
private readonly albumRepository: Repository<Album>,
) {}
async createOrUpdate(album: Album): Promise<Album> {
return await this.albumRepository.save(album);
}
async findOne(id: number): Promise<Album> {
return await this.albumRepository.findOne({ id: id });
}
async findAll(): Promise<Album[]> {
return await this.albumRepository.find();
}
async delete(id: number): Promise<DeleteResult> {
return await this.albumRepository.delete({ id: id });
}
}
# เขียน Controller
src/albums/album.controller.ts
import {
Controller,
Post,
Body,
Get,
Param,
HttpStatus,
HttpCode,
} from '@nestjs/common';
import { AlbumsService } from './albums.service';
import { CreateAlbumDto } from './dto/create-album.dto';
import { Album } from './entity/album.entity';
@Controller('albums')
export class AlbumsController {
constructor(private readonly albumService: AlbumsService) {}
@Post() // POST /albums
@HttpCode(HttpStatus.CREATED)
async createAlbum(@Body() newAlbum: CreateAlbumDto): Promise<Album> {
const album = new Album();
album.title = newAlbum.title;
album.remark = newAlbum.remark;
return await this.albumService.createOrUpdate(album);
}
@Get() // GET /albums
async findAlbums(): Promise<Album[]> {
return await this.albumService.findAll();
}
@Get(':id') // GET /albums/123
async findAlbum(@Param('id') id: number): Promise<Album> {
return await this.albumService.findOne(id);
}
@Put(':id') // PUT /albums/123
async updateAlbum(
@Param('id') id: number,
@Body() createAlbumDto: CreateAlbumDto,
): Promise<Album> {
const album = await this.albumService.findOne(id);
album.title = createAlbumDto.title;
album.remark = createAlbumDto.remark;
return await this.albumService.createOrUpdate(album);
}
@Delete(':id') // DELETE /albums/123
async deleteAlbum(@Param('id') id: number): Promise<any> {
await this.albumService.delete(id);
return { success: true };
}
}
โดย CreateAlbumDto เป็นตัวแทนข้อมูลที่รับมาจากผู้เรียกใช้งาน มีหน้าตาแบบนี้
src/albums/dto/create-album.dto.ts
export class CreateAlbumDto {
title: string;
remark: string;
}
โค้ดเบื้องต้นไม่มีการดัก Error อะไรทั้งนั้นนะครับด้วยความขี้เกียจ
# ทดลองที่เขียน
ลองสั่งเริ่มโปรแกรมดูครับ npm run start:debug
เมื่อรันแล้วจะพบไฟล์ app.sqlite
ลองเอามาเปิดดูใน SQLite Studio ก็จะพบกับตารางข้อมูล album ของเราโดยมีโครงสร้างข้อมูลแบบนี้
ลองใช้ Postman ยิง Post Request เพื่อสร้าง Album ดูครับ
POST localhost:3000/albums
ถ้าจะเอาข้อมูล Album ทั้งหมดก็แค่
GET localhost:3000/albums
ถ้าต้องการแค่ตัวเดียวล่ะ
GET localhost:3000/albums/1
ลองอัปเดตข้อมูลบ้าง
PUT localhost:3000/albums/1
ลบบ้าง
DELETE localhost:3000/albums/1
สร้างๆลบๆข้อมูลเล่น ข้อมูลเข้าจริงไหมนะ
ใครมีข้อสงสัยข้อแนะนำอะไรแนะนำได้ครับ
ดาวน์โหลด Source Code ที่นี่ (opens new window)