6.2 · Bootstrap + TypeORM 3 connections
บทนี้อธิบายจุดเริ่มต้นของ service ตั้งแต่ main.ts ไปจนถึงการ setup TypeORM connections ทั้ง 3 ตัวที่เชื่อมไปยัง MSSQL host เดียวกัน
src/main.ts:14-47 — bootstrap
14:47:benefit-api/src/main.ts
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: new I3GatewayLogger('System'),
});
// ...
const port = config.get('port') || 3000;
const prefix = `${process.env.BASE_PATH}api/v1`;
app.setGlobalPrefix(prefix);
app.useStaticAssets(join(process.cwd(), '/uploads'), {
prefix: '/benefit-api/',
});
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalGuards(new JwtAuthGuard(reflector));
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
app.enableCors({
origin: '*',
allowedHeaders: '*',
methods: 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
});
const document = SwaggerModule.createDocument(app, documentConfig);
SwaggerModule.setup(`${process.env.BASE_PATH}docs`, app, document);
await app.listen(port, '0.0.0.0', () => { /* ... */ });
}จุดที่ต้องสังเกต
| รายการ | ค่า | หมายเหตุ |
|---|---|---|
| Port | 9320 (จาก .env:5) | fallback 3000 ใน code ไม่เคยถูกใช้จริง |
| Global prefix | ${BASE_PATH}api/v1 → benefit-api/api/v1 | BASE_PATH จาก .env:3 |
| Swagger | ${BASE_PATH}docs → benefit-api/docs | เปิดให้ใครก็ได้โดยไม่ auth |
| Static assets | /uploads ที่ prefix /benefit-api/ | แสดงไฟล์ที่ upload สาธารณะ — ดู 6.8 S9 |
| Body limit | 50mb | ใหญ่เพราะรองรับ base64 file upload |
| Global guard | JwtAuthGuard (JWT) | แต่ bypass ด้วย @Public() เยอะมาก — ดู 6.5 |
| Global pipe | ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }) | ดี — แต่ถูก undermined โดย body: any ในบาง controller (เช่น studyLeave) |
| CORS | origin: '*', allowedHeaders: '*' | เปิดหมด — ดู 6.8 S7 |
| Helmet | ไม่ได้ติดตั้ง | grep helmet ไม่พบ |
| CSRF | ไม่ได้ติดตั้ง | ไม่มี csurf |
src/app.module.ts:40-57 — 3 TypeORM connections ผ่าน forRootAsync
40:57:benefit-api/src/app.module.ts
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
name: 'main',
useFactory: (configService: ConfigService) => configService.get('db'),
inject: [ConfigService],
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
name: 'vtrc',
useFactory: (configService: ConfigService) => configService.get('vtrcDB'),
inject: [ConfigService],
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
name: 'hrmi',
useFactory: (configService: ConfigService) => configService.get('dbHRMI'),
inject: [ConfigService],
}),แต่ละ connection โหลด config จาก key ต่างกันใน ConfigService ซึ่ง map กลับไปยัง config/configuration.ts
config/configuration.ts:8-61 — connection options
ทั้งสาม connections ใช้ pattern เดียวกัน ขอยกตัวอย่าง main connection:
8:25:benefit-api/config/configuration.ts
db: {
type: process.env.DB_TYPE,
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
autoLoadEntities: process.env.NODE_ENV === 'dev',
synchronize: process.env.NODE_ENV === 'dev',
logging: [`error`, `query`],
entities: [process.cwd() + '/dist/**/*.entity{.ts,.js}'],
extra: {
trustServerCertificate: true,
encrypt: false,
connectionTimeout: 15000,
requestTimeout: 15000,
},
} as TypeOrmModuleOptions,ตารางเปรียบเทียบ 3 connections
| Connection | name | DB host | DB name | synchronize | logging |
|---|---|---|---|---|---|
main (db) | 'main' | DB_HOST=10.188.128.61 | benefit | NODE_ENV === 'dev' | ['error','query'] |
| vtrcDB | 'vtrc' | VTRC_DB_HOST=10.188.128.61 | vtrc | false (hardcoded) | ['error','query'] |
| dbHRMI | 'hrmi' | DB_HRMI_HOST=10.188.128.61 | dbHRMI_Center | false (hardcoded) | ['error','query'] |
ข้อสังเกตสำคัญ
- ทั้งสาม connections ชี้ไปที่ MSSQL host เดียวกัน
10.188.128.61— เป็นคนละ database ใน instance เดียวกัน ไม่ใช่ separate server - ทั้งหมดล็อกอินด้วย
sa(DB_USER=sa,DB_PASSWORDเดียวกัน) — ดู 6.8 S11 trustServerCertificate: true+encrypt: falseสำหรับทุก connection — ปิด TLS และข้าม cert validationlogging: ['error', 'query']เปิดไว้ทุก connection รวมถึง production — ทุก SQL statement ถูก log (รวมถึง query ที่มี user input ที่ถูก interpolate เข้าไป) — ดู 6.8 S8synchronizeเปิดเฉพาะmainในNODE_ENV === 'dev'เท่านั้น — สำหรับvtrcและhrmiถูก hardcoded เป็นfalseเสมอ เพราะเป็น schema ของระบบอื่น
Dockerfile:1-30 — production image
1:30:benefit-api/Dockerfile
FROM node:20-alpine
WORKDIR /usr/src/app
ENV TZ="Asia/Bangkok"
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk-repositories && \
apk update && apk add --no-cache nmap harfbuzz freetype ttf-freefont nss chromium
RUN apk add chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
COPY package.json ./
ADD . .
RUN yarn
RUN yarn build
EXPOSE 9320
CMD ["yarn","start:prod"]ปัญหาของ Dockerfile
| ปัญหา | รายละเอียด |
|---|---|
ติดตั้ง nmap (บรรทัด 9) | nmap เป็น network scanning tool — ไม่มีเหตุผลที่ต้องอยู่ใน production runtime image — ขยาย attack surface ถ้ามี RCE |
| ใช้ Aliyun mirror (บรรทัด 8) | mirrors.aliyun.com เป็น mirror ของจีน — บ่งชี้ว่า build environment อยู่ในจีน |
| ติดตั้ง chromium สองรอบ (บรรทัด 8 และ 16) | redundant — ทิ้งขยะใน image |
ADD . . (บรรทัด 22) | copy ทุกอย่างรวมทั้ง .env เข้า image — ทำให้ .env ถูก bake เข้าไปแม้จะมี .dockerignore ก็ตาม (เพราะ .dockerignore มีแค่ yarn.lock) |
| Node version skew | Docker ใช้ node:20-alpine แต่ CI pipeline ใช้ node:16 (bitbucket-pipelines.yml:6) — build กับ runtime คนละ major version |
ลำดับการ startup
npm run start:prod
└─ node dist/main.js
└─ NestFactory.create(AppModule)
├─ ConfigModule.forRoot({ load: [configuration] })
├─ TypeOrmModule.forRootAsync('main') ← ต่อ MSSQL 'benefit'
├─ TypeOrmModule.forRootAsync('vtrc') ← ต่อ MSSQL 'vtrc'
├─ TypeOrmModule.forRootAsync('hrmi') ← ต่อ MSSQL 'dbHRMI_Center'
└─ 12 feature modules (forRoot + controllers + services)
└─ app.setGlobalPrefix('benefit-api/api/v1')
└─ app.useGlobalGuards(JwtAuthGuard)
└─ app.useGlobalPipes(ValidationPipe)
└─ SwaggerModule.setup('benefit-api/docs')
└─ app.listen(9320, '0.0.0.0')ทั้งสาม DB connections เปิดตอน NestJS bootstrap — ถ้า host 10.188.128.61 ไม่ตอบ, ทั้ง service ไม่ start