# ใช้ NestJS ต่อ Database อย่างไร?

#Dev#Javascript#Nestjs#Backend#Database#ORM#Library

เหมือนจะยากแต่ก็ไม่ยากอย่างที่คิด ในการต่อ Database นั้น ทางเลือกหลักๆที่เรามีก็ คือ Query เอาเอง หรือในบทความนี้ที่ผมจะมาใช้ TypeORM กันครับ

เจ้าตัว TypeORM เป็น Library สำหรับทำ ORM (Object Relational Mapping) ข้อดีสั้นๆก็คือเราไม่ต้องเขียน Query เองเลย

Header

เราสามารถ 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 เป็นต้น

Image

# ติดตั้งโปรแกรมอ่าน Database

ในที่นี้ผมจะใช้ SQLite Studio ครับ Download (opens new window)
ใช้เพื่อดูฐานข้อมูล SQLite ใครมีตัวเลือกสะดวกกว่านี้ก็ใช้ได้ครับ

Image

# ติดตั้ง Postman

Postman นั้นเป็นอีกหนึ่งในโปรแกรมยอดนิยมเลยครับใช้ในการทดสอบ Request ได้อย่างง่ายๆ Download (opens new window)
หากใช้ไม่เป็น อาจจะลองอ่านจาก ที่นี่ (opens new window)

Image

# มาเริ่มโค้ดกันเลย

สิ่งที่เราจะทำหลักๆเลยมีประมาณนี้ครับ

  1. ตั้งค่า TypeORM
  2. สร้าง Module ใหม่
  3. สร้าง Entity
  4. ประกอบร่าง
  5. ลองเขียน 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 ของเราโดยมีโครงสร้างข้อมูลแบบนี้

Image

ลองใช้ Postman ยิง Post Request เพื่อสร้าง Album ดูครับ

POST localhost:3000/albums

Image
แค่นี้เองก็ได้ Album แล้ว
Image
ลองสร้างอีกอัน

ถ้าจะเอาข้อมูล Album ทั้งหมดก็แค่
GET localhost:3000/albums

Image
ได้ข้อมูลละ

ถ้าต้องการแค่ตัวเดียวล่ะ
GET localhost:3000/albums/1

Image
ก็ได้มาเหมือนกัน

ลองอัปเดตข้อมูลบ้าง
PUT localhost:3000/albums/1

Image

ลบบ้าง
DELETE localhost:3000/albums/1

Image

สร้างๆลบๆข้อมูลเล่น ข้อมูลเข้าจริงไหมนะ

Image
เย่ มีข้อมูลจริงๆนะ

ใครมีข้อสงสัยข้อแนะนำอะไรแนะนำได้ครับ
ดาวน์โหลด Source Code ที่นี่ (opens new window)


อัปเดตเมื่อ: 1 ปีที่แล้ว