Skip to content

11.6 · centralize-api (NestJS) REST + Swagger

บทนี้รวบรวม REST endpoints ทั้ง 17 ตัวของ centralize-api (NestJS) พร้อม DTOs, auth bypass ผ่าน @Public() และ Swagger doc


ภาพรวม

ตัวเลขค่า
Controllers4
Endpoints17 (15 GET + 2 POST, 0 PUT/DELETE/PATCH)
DTO classes19
Services7
Custom repositories3
TypeORM connections2 (center + nbc)
Global prefixapi → ทุก path เริ่มด้วย /api/
Swagger docs/docs
Interceptors0 (ไม่มี response wrapper)

Bootstrap + global config

21:40:src/main.ts
  app.setGlobalPrefix('api')                              // line 21-23
  app.useGlobalGuards(new JwtAuthGuard(reflector))         // line 27
  app.useGlobalFilters(new HttpExceptionFilter())          // line 26
  app.useGlobalPipes(new ValidationPipe({                  // line 28-30
    whitelist: true,
    forbidNonWhitelisted: true,
  }))
  app.enableCors({ origin: '*', allowedHeaders: '*', methods: 'GET,PUT,POST,DELETE,PATCH,OPTIONS' })  // line 31-35
  
  const document = SwaggerModule.createDocument(app, documentConfig)   // line 37
  SwaggerModule.setup('docs', app, document)                          // line 38

Swagger config

1:10:docs/document.config.ts
export const documentConfig = new DocumentBuilder()
  .setTitle('Centralize API')
  .setDescription('Centralize API')
  .setVersion('1.0')
  .addServer(`http://10.188.128.160:${process.env.PORT || 3000}/`)
  .addServer(`http://localhost:${process.env.PORT || 3000}/`)
  .addBearerAuth()
  .build()
  • Title: Centralize API
  • Version: 1.0
  • Servers: LAN (prod) + localhost
  • Auth scheme: Bearer JWT (แต่ถูก bypass จริง)

Controllers — 4 ตัว

Controllerไฟล์@ApiTags@Controller(...)Endpoints
AppControllersrc/app.controller.tsRoot(root)1
EmployeeControllersrc/modules/employee/employee.controller.tsEmployeeemployee14
HrPaySlipControllersrc/modules/hrPaySlip/hrpayslip.controller.tsHR Pay SliphrPaySlip1
HrTimeTempControllersrc/modules/hrTimeTemp/hrtimetemp.controller.tsHR Time TemphrTimeTemp1

Endpoints ทั้งหมด — 17 ตัว

AppController — 1 endpoint

#MethodPathHandlerบรรทัดReturn
1GET/api/getHelloapp.controller.ts:26-30RootObject[] — org tree

EmployeeController — 14 endpoints (ทั้งหมด @Public())

#MethodPathHandlerบรรทัดReturn
2GET/api/employeegetEmployees38-42vtrcMainProfile[]
3GET/api/employee/cugetEmployeesCU44-48vtrcAllProfile[]
4GET/api/employee/managergetManagers50-54manager list
5GET/api/employee/posManagegetPositionManagers56-60position manager list
6GET/api/employee/positiongetPositions62-66position list
7GET/api/employee/divisiongetDivisions68-72diviCode, diviName, sourceDB
8GET/api/employee/departmentgetDepartments74-78deptCode, deptName, sourceDB
9GET/api/employee/groupgetEmployeeGroups80-84employee group list
10GET/api/employee/levelgetEmployeeLevels86-90employee level list
11GET/api/employee/posTechnicalgetTechnicalPositions92-96technical position list
12GET/api/employee/organizegetOrganizations98-102org list
13GET/api/employee/getByEmpCodegetEmployeeByEmployeeCode104-108single row
14GET/api/employee/getEmployeeDisastergetEmployeeDisaster110-114disaster filter
15GET/api/employee/bankAccountgetBankAccount116-120bank + branch

HrPaySlipController — 1 endpoint

#MethodPathHandlerบรรทัดReturn
16POST/api/hrPaySlipgetPaySlipDThrpayslip.controller.ts:17-21payslip detail rows

HrTimeTempController — 1 endpoint

#MethodPathHandlerบรรทัดReturn
17POST/api/hrTimeTemp/importgetShiftTimeWithWorkhrtimetemp.controller.ts:17-21{ date, timeIn, timeOut }[]

ตัวอย่าง endpoint signatures

GET /api/employee — ค้นหาพนักงาน

ts
// employee.controller.ts:38-42
@Public()
@Get()
getEmployees(@Query() query: GetEmployeeQueryDto) {
  return this.employeeService.getEmployees(query)
}

GET /api/employee/getByEmpCode — รายบุคคล

ts
// employee.controller.ts:104-108
@Public()
@Get('getByEmpCode')
getEmployeeByEmployeeCode(@Query() query: GetEmployeeByEmpCodeDto) {
  return this.employeeService.getEmployeeByEmployeeCode(query)
}

POST /api/hrPaySlip — payslip batch

ts
// hrpayslip.controller.ts:17-21
@Public()
@Post()
getPaySlipDT(@Body() body: EmpCodeListDto) {
  return this.hrPaySlipService.getPaySlipDT(body)
}

POST /api/hrTimeTemp/import — shift time

ts
// hrtimetemp.controller.ts:17-21
@Public()
@Post('import')
getShiftTimeWithWork(@Body() body: GethrTimeTempQueryDto) {
  return this.hrTimeTempService.getShiftTimeWithWork(body)
}

DTOs — 19 classes

ไฟล์: src/dto/employee.dto.ts (16 classes), hrpayslip.dto.ts (1), hrTimeTemp.dto.ts (1 DTO + 2 interfaces), test.dto.ts (1 — boilerplate leftover)

Validation decorators ที่ใช้

@IsString, @IsOptional, @IsNotEmpty, @IsDate, @IsInt, @IsArray, @IsDateString, @ArrayMinSize, @Type(() => Date) (จาก class-transformer)

ตัวอย่าง DTO

GetEmployeeQueryDto — 9 optional string fields:

25:70:src/dto/employee.dto.ts
export class GetEmployeeQueryDto {
  @ApiProperty({ required: false })
  @IsString()
  @IsOptional()
  department?: string;
  
  @ApiProperty({ required: false })
  @IsString()
  @IsOptional()
  sourceDB?: string;
  
  // ... diviCode, orgCode, posManageID, positionCode, empCode, groupCode, levelCode
}

EmpCodeListDto:

1:7:src/dto/hrpayslip.dto.ts
import { IsArray, IsString } from 'class-validator';

export class EmpCodeListDto {
  @IsArray()
  @IsString({ each: true })
  empCode: string[];
}

GethrTimeTempQueryDto:

9:18:src/dto/hrTimeTemp.dto.ts
export class GethrTimeTempQueryDto {
  @IsString()
  @IsNotEmpty()
  empCode: string;

  @IsArray()
  @IsDateString(null, { each: true })
  @ArrayMinSize(1)
  workingDate: Date[];
}

Global ValidationPipe

ts
// src/main.ts:28-30
new ValidationPipe({
  whitelist: true,           // ลบ field ที่ไม่มีใน DTO
  forbidNonWhitelisted: true, // throw ถ้ามี field ที่ไม่มีใน DTO
})

ผล: request ที่ส่ง field แปลกปลอม → 400 Bad Request


Auth — JWT guard ที่ bypassed ทุก route

Guard setup

27:27:src/main.ts
app.useGlobalGuards(new JwtAuthGuard(reflector))

Guard logic

22:35:libs/jwt/jwt-auth.guard.ts
canActivate(context: ExecutionContext) {
  const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
    context.getHandler(),
    context.getClass(),
  ])
  if (isPublic) {
    return true  // ข้าม JWT ทั้งหมด
  }
  return super.canActivate(context)
}

@Public() decorator

53:54:libs/jwt/jwt-auth.guard.ts
export const isPublicKey = 'isPublic';
export const Public = () => SetMetadata(isPublicKey, true);

การใช้ — ทุก route

ไฟล์บรรทัด
src/app.controller.ts26
src/modules/employee/employee.controller.ts38, 44, 50, 56, 62, 68, 74, 80, 86, 92, 98, 104, 110, 116 (ทุก method)
src/modules/hrPaySlip/hrpayslip.controller.ts17
src/modules/hrTimeTemp/hrtimetemp.controller.ts17

ผล: ไม่มี endpoint ใดต้อง JWT — ทุกคนที่เข้าถึง centralize-api สามารถเรียกได้

JWT strategy (เผื่อเปิดใช้ในอนาคต)

7:24:libs/jwt/jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: config.get('jwtSecretKey'),  // process.env.SECRET_KEY
    })
  }
  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username }
  }
}

Services — 7 ตัว + 2 query patterns

Serviceไฟล์Injection
AppServicesrc/app.service.tsEntityManager('center')
EmployeeServicesrc/modules/employee/employee.service.tsEntityManager('center'), TechnicalPositionRepository
HrPaySlipServicesrc/modules/hrPaySlip/hrpayslip.service.tsEntityManager('center')
HrTimeTempServicesrc/modules/hrTimeTemp/hrtimetemp.service.ts2 EntityManagers: 'center' + 'nbc'
TechnicalPositionServicesrc/modules/technicalPosition/technicalPosition.service.tsTechnicalPositionRepository
EmployeeLevelServicesrc/modules/employeeLevel/employeeLevel.service.tsEmployeeLevelRepository
EmployeeGroupServicesrc/modules/employeeGroup/employeeGroup.service.tsEmployeeGroupRepository

Pattern A — Raw SQL (EntityManager.query)

ใช้ใน AppService, EmployeeService, HrPaySlipService, HrTimeTempService:

ts
// employee.service.ts:181-184
return this.entityManager.query(fuckQuery, [
  query.sourceDB,
  query.orgCode,
])

ส่วนใหญ่เป็น SQL ที่เขียนเอง (hand-written) มี cross-database join (dbHRMI_Center.dbo.X UNION ALL dbHRMI_Center_NBC.dbo.X) + WITH (NOLOCK)

Pattern B — TypeORM ActiveRecord

ใช้ใน 3 repositories:

ts
// technicalPosition.repository.ts:20-21
const center = await TechnicalPosition.findBy(...)
const nbc = await TechnicalPositionNBC.findBy(...)
return center.concat(nbc)

SourceDB tenancy — SQL-level UNION

centralize-api มี 2 TypeORM connections:

ConnectionDBEnv
'center'dbHRMI_CenterDB_NAME
'nbc'dbHRMI_Center_NBCDB_NAME_NBC

Sharding patterns

1. SQL-level UNION ALL (ส่วนใหญ่)

sql
-- employee.service.ts:512-518 (ตัวอย่าง concept)
SELECT PosManageID, PosManageCode, PosManageName, SourceDB 
FROM dbHRMI_Center.dbo.emPositionManage_reStructure
UNION ALL
SELECT PosManageID, PosManageCode, PosManageName, SourceDB 
FROM dbHRMI_Center_NBC.dbo.emPositionManage_reStructure

2. Application-level Promise.all

ts
// hrtimetemp.service.ts:35-71
const [center, nbc] = await Promise.all([
  this.centerManager.query(sql, params),
  this.nbcManager.query(sql, params),
])
return center.concat(nbc)

3. Repository-level parallel find

ts
// technicalPosition.repository.ts:19-23
const [center, nbc] = await Promise.all([
  TechnicalPosition.find({ where }),
  TechnicalPositionNBC.find({ where }),
])
return center.concat(nbc)

SourceDB value mapping

ts
// app.service.ts:15-25 + employee.service.ts:37-42
| SourceDB literal       | OrgCode |
|------------------------|---------|
| dbHRMI_CU_SC_rep       | 02CS    |
| dbHRMI_CU_H_rep        | 02      |
| dbHRMI_RC_C_rep        | 03      |
| dbHRMI_RC_B_rep        | 00      |
| dbHRMI_RC_NBC_rep      | 07      |

Error model

Global exception filter

ts
// src/main.ts:26
app.useGlobalFilters(new HttpExceptionFilter())
ts
// libs/exception/http-exception.filter.ts:21-25
response.status(status).json({
  statusCode: status,
  message: message['message'] || message['error'],
})

Response shape

json
{
  "statusCode": 400,
  "message": "<string หรือ string[]>"
}

ไม่มี error, data, timestamp, path — มี 2 field เท่านั้น

Status codes ที่ใช้

Statusเมื่อไร
400ทุก error ใน service (รวม DB error) — แปลงเป็น BadRequestException
401JWT auth fail (แต่ไม่เกิดเพราะ @Public() ทุก route)
(validation)ValidationPipe auto — 400

ความผิดปกติ: DB error ถูกแปลงเป็น 400 แทน 500 — อาจทำให้ debug สับสน


Response format — ไม่มี wrapper

  • ไม่มี interceptor
  • ไม่มี { data, status, message } envelope
  • Response คือสิ่งที่ service return — serialize เป็น JSON โดยตรง

ตัวอย่าง response:

json
[
  { "empCode": "12345", "fullName": "สมชาย", "sourceDB": "dbHRMI_RC_B_rep", ... },
  ...
]

CORS

31:35:src/main.ts
app.enableCors({
  origin: '*',
  allowedHeaders: '*',
  methods: 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
})

origin: '*' — เปิดรับทุก origin (เสี่ยงถ้าเปิดสู่ internet)


Swagger — ข้อจำกัด

สิ่งที่ Swagger แสดง

  • Auto-generated DTO schema (จาก class-validator ผ่าน nest CLI plugin)
  • @ApiTags (grouping)
  • @ApiBearerAuth badge (แต่ bypass จริง)

สิ่งที่ Swagger ไม่แสดง

  • ไม่มี @ApiOperation — ไม่มี description ของ endpoint
  • ไม่มี @ApiResponse — ไม่มี response example
  • ไม่มี @ApiBody, @ApiQuery, @ApiParam — ไม่มี param description

ผล: Swagger doc มีแค่ skeleton — ไม่ครบข้อมูล


ข้อระวังทั้งหมด

1. JWT guard ไม่ทำงานจริง

  • ทุก route @Public() → ไม่ต้อง auth
  • ถ้าจะเปิด auth ต้องลบ @Public() ออกจาก method นั้น

2. Query SQL มี cross-DB join

  • dbHRMI_Center.dbo.X UNION ALL dbHRMI_Center_NBC.dbo.X
  • ถ้าเปลี่ยนชื่อ DB — ต้องแก้ SQL ทุกที่
  • มี WITH (NOLOCK) ทั่วไป — ระวัง dirty read

3. CORS เปิดหมด

  • origin: '*' + allowedHeaders: '*'
  • ถ้าเปิดสู่ internet — อันตราย

4. DB error → 400

  • ทุก service catch block แปลงเป็น BadRequestException
  • ทำให้ client คิดว่าเป็น validation error แต่จริง ๆ อาจเป็น DB down

5. ไม่มี 404

  • ถ้า query ไม่เจอ → return [] (empty array)
  • client ต้องเช็คเองว่า empty แปลว่าไม่มีข้อมูล

6. Dead DTO

  • src/dto/test.dto.ts เป็น NestJS boilerplate leftover — ไม่ได้ใช้

7. Custom validator ไม่ได้ใช้

  • libs/common/validator-date.ts:7-22 (IsOnlyDate) — ไม่ได้ wire เข้า DTO ใด

เปรียบเทียบกับ cu-central-api

ด้านcentralize-apicu-central-api
ภาษาTypeScriptJavaScript
FrameworkNestJS 9Apollo + Express
API typeRESTGraphQL + REST
Endpoints1788 (83 GraphQL + 5 REST)
AuthJWT guard (bypassed)JWT + apiKey + LDAP
ORMTypeORMSequelize
DBMSSQL (2 conns)MSSQL (5 conns)
ShardingSQL UNION (2 DBs)SourceDB column
DocsSwagger /docsไม่มี
Validationclass-validatorGraphQL schema
Callerไม่ยืนยันvtrc-api

ขั้นตอนถัดไป

ไป บท 10.7 Error model — 3 services เทียบกัน