ต้องบอกว่าผมเองก็อยู่กับเฟรมเวิร์ก Laravel ที่ใช้ภาษา PHP มานาน การที่ตัวเองมาต้องออกจาก Comfort Zone มาเขียน API ด้วยเฟรมเวิร์กภาษา TypeScript อย่าง NestJS เป็นเรื่องที่ท้าทายเป็นอย่างมาก ทั้งเรื่องโครงสร้างไฟล์ที่แตกต่าง รวมถึง Syntax ของ TypeScript ที่ต้องบอกเลยว่า อาจจะต้องปรับตัวกันเยอะหน่อยนะ
ผมโชคดีจังเลยที่ตอนเรียนในมหาวิทยาลัยได้เรียนภาษา Java ซึ่งเป็นภาษาที่เน้นเรื่องการใช้ Static Type และ Object-orented Programming และการเขียนเว็บ Frontend ด้วย JavaScript ทำให้ผมนำความรู้ที่มีมาใช้ในการเขียน RESTFul API เพื่อเชื่อมต่อฐานข้อมูลครับ
Technology Stack ที่ใช้
Stack ที่ผมใช้มาทำเป็น Example ก็จะมี
- NestJS เป็น Framework ภาษา TypeScript ที่มีฟังก์ชันให้เลือกใช้อย่างครบครันพอ ๆ กับ Laravel ที่เขียนอยู่เลยครับ
- MongoDB เป็นฐานข้อมูลแบบ NoSQL ที่ให้อิสระในการออกแบบข้อมูล ซึ่งทำให้ผมแทบจะเลิกสนใจการออกแบบฐานข้อมูลใน MySQL/MariaDB ไปได้
- Docker เป็น Container Engine ที่ทำให้ผมเลิกใช้ MAMP XAMPP ในการเขียนโค้ดภาษา PHP ตลอดกาล
วิธีสร้างโปรเจกต์ NestJS
เริ่มต้นจากลง Node.js และ Yarn ในเครื่องให้ได้ก่อน ละก็ ติดตั้ง NestJS CLI ด้วยคำสั่งนี้ครับ
yarn global add @nestjs/cli
ติดตั้งสิ่งที่จำเป็นในการทำ API กันแล้วมาสร้างโปรเจกต์ ด้วยคำสั่งนี้ครับ (แทนที่ข้อความ nestjs-mongodb
ด้วยชื่อโปรเจกต์คุณเลยครับ)
nest new nestjs-mongodb
ทีนี้มันจะถามครับว่าใช้ตัวจัดการแพคเกจอะไรกับโปรเจกต์นี้ เลือก yarn
ได้เลยครับ
สร้างโปรเจกต์กันเสร็จแล้ว ทีนี้เราจะลงแพคเกจที่จำเป็นเพิ่ม ก็จะมี
typeorm
เป็นแพคเกจที่ช่วยในการเขียน Query เพื่อเรียกข้อมูลในฐานข้อมูลด้วย Syntax ภาษา TypeScript ซึ่งใช้ได้ทั้งฐานข้อมูล MySQL MariaDB และ MongoDB อีกด้วยครับ@nestjs/typeorm
เป็นแพคเกจที่จะทำให้ TypeORM สามารถใช้ร่วมกับ NestJS ได้ครับmongodb
อันนี้เป็น Engine ที่ทำให้ TypeORM สามารถต่อฐานข้อมูล TypeORM ได้ครับ@nestjs/config
เป็นแพคเกจที่ทำให้สามารถอ่านค่าจากไฟล์ .env มาใช้ในโค้ดได้ครับ
ทีนี้มาลง Package เสริมในโปรเจกต์เรากันครับ
yarn add @nestjs/config @nestjs/typeorm typeorm mongodb
วิธีเรียกใช้ค่า .env ใน main.ts ของ NestJS
วิธีที่ทำให้สามารถใช้ .env
ใน NestJS ได้ เรามาเริ่มจากการสร้างไฟล์ .env ที่ Root ของ Project นี้เลยครับ จากนั้นก็เขียนตาม Syntax ได้ประมาณนี้ครับ ส่วนค่าของฐานข้อมูลที่จะใส่ก็ตามที่ตั้งค่าไว้เลยครับ
APP_PORT=3000
DATABASE_HOST=localhost
DATABASE_PORT=27017
DATABASE_USERNAME=root
DATABASE_PASSWORD=phongphan.me
DATABASE_NAME=nestjs-mongodb
ได้เวลามาเขียน TypeScript แล้วครับ มาเริ่มต้นจากการให้สามารถเรียก API จาก NestJS ด้วย Port ที่กำหนดใน .env
ให้ไปยัง main.ts
จะเจอโค้ดหน้าตาประมาณนี้ครับ
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
วิธีการเรียกค่าจาก .env
ใน src/main.ts
ไม่ยากเลยครับ เริ่มต้นจากการเพิ่ม Statement เพื่อ Import คลาส ConfigService
ของแพคเกจ @nestjs/config
import { ConfigService } from "@nestjs/config"; //เพิ่มบรรทัดนี้
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
จากนั้น เพิ่ม Statement นี้เพื่อเรียกค่า Config จาก ConfigService ครับ
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService); //เพิ่มบรรทัดนี้
await app.listen(3000);
}
เปลี่ยนเลข Port ที่เขียนฝังไว้ในโค้ดด้วย Statement configService.get<number>("APP_PORT", 3000)
ครับ
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
await app.listen(configService.get<number>("APP_PORT", 3000)); //แก้บรรทัดนี้
}
จาก Statement ด้านบน
number
ก็คือชื่อ Data Type ตัวเลขเพื่อบอกว่า ค่าที่เรียกมาจาก Config นั้นเป็นตัวเลขนะ เราก็จะเปลี่ยนไปตามค่า Config ที่เราใส่อาจจะเป็นอย่างอื่น เช่นstring
หรือboolean
ขึ้นอยู่กับว่าฟังก์ชันนั้นต้องการรับ Parameter หรือตัวแปรที่ตั้งไว้ เป็น Data Type ชนิดอะไรAPP_PORT
ก็คือชื่อ Config ใน.env
ที่เราต้องการนำค่ามาใส่3000
จะใส่หรือไม่ใส่ก็ได้นะ มันคือค่าเริ่มต้น (Default) เมื่อไม่เจอชื่อ Config ที่ระบุใน .env ครับ มันก็จะหยิบค่า Default มาใช้แทน อันนี้มีประโยชน์ตอนที่มัน Error ได้ตอนที่หาค่า Config ไม่เจอครับ โดยที่ Data Type ของ Parameter นี้ต้องตรงกับที่ระบุไว้ใน Generic Type ของฟังก์ชันด้วยนะครับ ก็คือค่าในวงเล็บ <…> นี่ละครับ
สุดท้าย ไปยัง src/app.module.ts
เพื่อให้ทั้งโปรเจกต์สามารถเรียกใช้ Config จาก .env ได้ เราต้องเรียกใช้ ConfigModule.forRoot()
เข้าไปยัง Array ของ Property imports
ใน Object ของ Decorator @Module
ด้วยนะครับ
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [
ConfigModule.forRoot() //เพิ่มบรรทัดนี้
],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
การเรียกใช้ค่า .env คู่กับการเชื่อมต่อฐานข้อมูลด้วย TypeORM
เพื่อให้เห็นภาพมากขึ้นในการเรียกใช้ Config ใน ไฟล์ .env
ผมจะขอยกตัวอย่างในการเชื่อมต่อฐานข้อมูลด้วย TypeORM ใน AppModule
ประมาณนี้ครับ
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: "mongodb",
host: configService.get<string>("DATABASE_HOST", "localhost"),
port: configService.get<number>("DATABASE_PORT", 27017),
authSource: "admin",
username: configService.get<string>(
"DATABASE_USERNAME",
"phongphan.me"
),
password: configService.get<string>("DATABASE_PASSWORD", ""),
database: configService.get<string>(
"DATABASE_NAME",
"nestjs-mongodb"
),
autoLoadEntities: true
}),
inject: [ConfigService]
}),
ConfigModule.forRoot()
],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
จะเห็นว่า ตอนที่เราเรียก Module ของ TypeORM ด้วย TypeOrmModule.forRootAsync
จะประกอบด้วย 3 Property เลยคือ
imports
เราต้อง Import คลาสConfigModule
เพื่อให้ AppModule สามารถเอาค่า Option ของ TypeORM ที่ตั้งใน PropertyuseFactory
มาใช้กับ NestJS ได้useFactory
เหมือนเป็นการสร้าง Factory Function เพื่อให้สามารถนำค่าที่ Config ภายในฟังก์ชันไปเป็นส่วนหนึ่งของ TypeORM ซึ่งในทีนี้ เราสามารถที่จะเขียน Logic บางอย่างเข้าไปได้ หรือถ้าทำให้โค้ดดูรกเกินไป สามารถเขียนคลาส Service เพิ่มเพื่อให้โค้ดดู Clean ได้ และเรียกใช้คลาสที่เขียนด้วย PropertyuseClass
แทนได้inject
จะเป็นการเอาคลาส Service ก็คือConfigService
มาใช้ใน Factory Function เพื่อให้สามารถเรียกใช้ค่า Config ในฟังก์ชันได้ หรือถ้าใช้ PropertyuseClass
Propertyinject
อาจไม่ต้องใช้ก็ได้ครับ
ทีเหลือก็ต้องเขียน Repository Service รวมถึงเขียน Controller เพื่อให้สามารถเรียกใช้ API ได้ ซึ่งการใช้ TypeORM คู่กับ MongoDB ใช้ไม่ยากเลยครับ ขอจบบทความนี้ด้วยรูปการยิง API ซึ่งเป็นผลที่ได้จากการเรียนรู้ด้วยตนเองของผม หวังว่าจะเป็นประโยชน์ให้คนที่มือใหม่เหมือนกันนะครับ
