11.7 · Error model — 3 services เทียบกัน
บทนี้รวบรวม error code, error response shape, และ HTTP status ของทั้ง 3 services ไว้ในที่เดียว เพื่อให้ frontend และ client อื่น ๆ จัดการ error ได้ถูกต้อง
ภาพรวม — แต่ละ service จัดการ error อย่างไร
| Service | Error format | ที่เก็บ code | HTTP status |
|---|---|---|---|
| vtrc-api (GraphQL) | { errors: [{ message, extensions: { code } }] } | lib/responErrors.js:31-229 | n/a (GraphQL ใช้ 200 เสมอ) |
| vtrc-api (REST) | ไม่มี envelope — sendStatus(404/400/500) | inline | 200/400/404/500 |
| cu-central-api (GraphQL) | { errors: [{ message, extensions: { code } }] } | lib/responseAndLog.js:55-128 | n/a |
| cu-central-api (REST) | { error: code, errorText: "...", errorVariable: {}, responsedAt: "..." } | lib/auth/auth.js:292-301 | 200/400 |
| centralize-api | { statusCode: 400, message: "..." } | libs/exception/http-exception.filter.ts | 200/400/401 |
1. vtrc-api error model
GraphQL error shape
json
{
"errors": [
{
"message": "Session ถูกขัดจังหวะ หรือไม่ถูกต้อง",
"extensions": {
"code": "TOKEN_INVALID",
"exception": { "stacktrace": ["..."] }
}
}
],
"data": null
}Error factory
js
// lib/responErrors.js:3-29
export const responError = (code, args, type, message, allowArgs) => {
// throw AuthenticationError / UserInputError / ApolloError
// ตาม type
}Error code catalog — auth/token codes
| Code | Message | เมื่อไร | Frontend action |
|---|---|---|---|
ACCESS_KEY_INVALID | "Access Key is invalid." | apiKey ผิด | ตรวจ apiKey config |
TOKEN_INVALID | "Session ถูกขัดจังหวะ หรือไม่ถูกต้อง" | JWT/session ผิด | logout + login |
TOKEN_EXPIRED | "Token is expired." | JWT หมดอายุ | refresh token |
SESSION_EXPIRED | "Session is expired." | session หมดอายุ | login ใหม่ |
SESSION_INVALID | "Session is invalid." | session ผิด | login ใหม่ |
SESSION_ACCESS_LIMIT | "Session limitation" | เกิน concurrent session | logout ที่อื่น |
AUTH_USER_OR_PASS_INVALID | (custom msg) | credentials ผิด | แจ้ง user |
AUTH_LIMIT | "ระบุเกินจำนวนครั้งที่กำหนด..." | login ผิดเกินจำกัด | รอ + ลองใหม่ |
CANNOT_ACCESS | "คุณไม่มีสิทธิ์ในการเข้าถึง" | role/menu ผิด | แจ้ง admin |
UNAUTHENTICATED | (จาก cu-central-api) | forward ได้ UNAUTHENTICATED | mapped เป็น TOKEN_INVALID |
INTERNAL_SERVER_ERROR | "บางสิ่งทำงานไม่ถูกต้อง..." | fallback | ลองใหม่ |
Error code catalog — อื่น ๆ
| Code group | ตัวอย่าง |
|---|---|
| Centralize (forward) | 16 codes (CENTRALIZE_*) |
| 3rd party (Conicle) | 3 codes |
| Study leave | MSG001 — MSG015 (Thai messages) |
| GraphQL validation | GRAPHQL_VALIDATION_FAILED |
ที่มา: lib/responErrors.js:31-229
Production error masking
js
// lib/responErrors.js:168-191 + index.js:61-64
// ทำงานเฉพาะ production (IS_PRODUCTION=true)
const graphqlFormatError = (err) => {
// 1. "Cannot return null for non-nullable field" → INTERNAL_SERVER_ERROR
// 2. GRAPHQL_VALIDATION_FAILED → "Validate failed. Cannot query."
// 3. SequelizeDatabaseError → responseQueryError (log เป็น SQL_ERROR)
// 4. อื่น ๆ → ผ่านตามเดิม
}REST error — ไม่มี envelope
js
// ตัวอย่างใน routes.js
res.sendStatus(404) // auth fail หรือ file missing
res.sendStatus(400) // validation error
res.sendStatus(500) // server errorปัญหา: ใช้ 404 เป็น "auth failed" บ่อย — ทำให้ debug สับสน
2. cu-central-api error model
GraphQL error shape
เหมือน vtrc-api (Apollo 2 ทั้งคู่):
json
{
"errors": [
{
"message": "...",
"extensions": { "code": "PROFILE_NOT_FOUND", "exception": { ... } }
}
],
"data": null
}Error factory
js
// lib/responseAndLog.js:11-37
responseError(text, args, errorCode)
// throw UserInputError / ValidationError / AuthenticationError / ApolloErrorError code catalog — ~40 codes
| Group | Codes |
|---|---|
| Server | INTERNAL_SERVER_ERROR |
| Format | FORMAT_DATE_INVALID, MONTH_INVALID, YEAR_INVALID |
| Profile | PROFILE_NOT_EXISTS, PROFILE_NOT_FOUND, PROFILE_NOT_COMPLETE, EMPLOYEE_NOT_FOUND |
| Account | ACCOUNT_NOT_EXISTS, ACCOUNT_INVALID |
| Password | PASSWORD_* (8 codes) — PASSWORD_TOO_SHORT, PASSWORD_NOT_MATCH, ... |
EMAIL_NOT_EXISTS, EMAIL_INVALID_* | |
| Phone | PHONE_NUMBER_* |
| SMS | SMS_* |
| OTP | OTP_* |
| Verify | VERIFY_INVALID, VERIFY_CODE_* (3 codes) |
| Payroll | PAYROLL_SLIP_NOT_EXISTS |
| Document | DOCUMENT_NOT_FOUND |
| Data | DATA_INVALID, DATA_NOT_FOUND |
| Custom | MSG014, MSG015 (Thai messages) |
ที่มา: lib/responseAndLog.js:55-128
REST auth error shape
json
{
"error": "AUTH_USER_OR_PASS_INVALID",
"errorText": "Username or Password is invalid.",
"errorVariable": {},
"responsedAt": "2026-07-10T15:00:00.000Z"
}js
// lib/auth/auth.js:264-272
error: (code, variables = {}) => {
const errorCodes = AuthErrorCodes()
return {
error: code,
errorText: errorCodes[code] || '',
errorVariable: variables,
responsedAt: AuthResponse.getResponsedAt()
}
}REST auth codes
| Code | Text |
|---|---|
AUTH_REQUIRED_PARAMS | Required some parameter. |
AUTH_USER_OR_PASS_INVALID | Username or Password is invalid. |
AUTH_USER_NOT_ALLOWED | This user is not allowed. |
REFRESH_TOKEN_MIS_MATCH | Refresh token mismatch. |
ACCESS_TOKEN_INVALID | Access token is invalid. |
AUTH_API_KEY_INVALID | API KEY INVALID |
INTENAL_SERVER_ERROR | (typo — ควรเป็น INTERNAL) Session generation failed. |
ข้อระวัง: typo INTENAL_SERVER_ERROR ติดอยู่ใน schema — เปลี่ยนไม่ได้เพราะ client อาจใช้อยู่
3. centralize-api error model
Global exception filter
ts
// libs/exception/http-exception.filter.ts:21-25
response.status(status).json({
statusCode: status,
message: message['message'] || message['error'],
})Error shape
json
{
"statusCode": 400,
"message": "<string หรือ string[]>"
}เพียง 2 field — statusCode + message
Status codes ที่ใช้จริง
| Status | เมื่อไร |
|---|---|
| 400 | ทุก error ใน service (DB error รวมอยู่ด้วย) |
| 401 | JWT fail (ไม่เกิดเพราะ @Public()) |
จุดผิดปกติ
- DB error → 400 — ควรเป็น 500 แต่ catch block แปลงเป็น
BadRequestExceptionหมด - ไม่มี 404 — query ไม่เจอ return
[]ไม่ throw - ไม่มี 403, 409 — ไม่ได้ใช้
เปรียบเทียบ 3 services — error code overlap
| Concept | vtrc-api | cu-central-api | centralize-api |
|---|---|---|---|
| Auth fail | TOKEN_INVALID, TOKEN_EXPIRED, SESSION_* | ACCESS_TOKEN_INVALID, AUTH_* | 401 (ไม่เกิด) |
| apiKey ผิด | ACCESS_KEY_INVALID | AUTH_API_KEY_INVALID | n/a |
| Not found | (varies) | *_NOT_FOUND, *_NOT_EXISTS | ไม่มี (return []) |
| Validation | UserInputError + custom | FORMAT_*_INVALID, DATA_INVALID | class-validator auto → 400 |
| Server error | INTERNAL_SERVER_ERROR | INTERNAL_SERVER_ERROR | 400 (แปลงจาก 500) |
Frontend จัดการ error อย่างไร
vtrc-web / vtrc-rc-backoffice — errorLink
js
// ตัวอย่าง concept จาก vtrc-web/src/config/apollo.js
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
case 'TOKEN_EXPIRED':
// refresh token + replay request
return forward(operation)
case 'SESSION_EXPIRED':
case 'TOKEN_INVALID':
case 'SESSION_INVALID':
alert('เซสชันหมดอายุ กรุณาเข้าสู่ระบบใหม่')
localStorage.clear()
window.location.href = '/login'
break
case 'ACCESS_KEY_INVALID':
alert('แอปไม่ถูกต้อง')
break
case 'CANNOT_ACCESS':
alert('คุณไม่มีสิทธิ์ในการเข้าถึง')
break
default:
console.warn('GraphQL error:', err)
}
}
}
if (networkError) {
if (networkError.statusCode === 401) {
// เหมือน TOKEN_EXPIRED
} else {
alert('เครือข่ายมีปัญหา กรุณาลองใหม่')
}
}
})ไฟล์ error → ข้อความไทย
js
// vtrc-rc-backoffice/src/utils/checkError.js:1-21
const checkError = (code) => {
const map = {
TOKEN_EXPIRED: 'เซสชันหมดอายุ กรุณาเข้าสู่ระบบใหม่',
TOKEN_INVALID: 'การเข้าสู่ระบบไม่ถูกต้อง',
SESSION_EXPIRED: 'เซสชันหมดอายุ',
CANNOT_ACCESS: 'คุณไม่มีสิทธิ์ในการเข้าถึง',
// ...
}
return map[code] || 'เกิดข้อผิดพลาด กรุณาลองใหม่'
}Flow ของ error จาก cu-central-api → vtrc-api → frontend
[1] cu-central-api throw error (GraphQL)
↓
[2] vtrc-api รับ error ผ่าน GraphqlCDWithToken
↓
[3] vtrc-api map:
- UNAUTHENTICATED → TOKEN_INVALID
- อื่น ๆ → ผ่านตามเดิม
↓
[4] vtrc-api throw ใหม่ (เป็น Apollo error ของ vtrc-api)
↓
[5] Frontend errorLink จับ error
↓
[6] แมป code → ข้อความไทย → alert userดูรายละเอียด forward pattern ใน บท 10.9
ข้อระวังทั้งหมด
1. Code บางตัว typo ติดใน schema
| Code | ปัญหา |
|---|---|
INTENAL_SERVER_ERROR (cu-central-api) | ควรเป็น INTERNAL |
Subscribtion (cu-central-api type) | ควรเป็น Subscription (ไม่ใช่ error code แต่เกี่ยวข้อง) |
การแก้ไขทำให้ client เดิมพัง → ต้อง coordinate
2. REST ของ vtrc-api ใช้ 404 ผิดวัตถุประสงค์
- ใช้ 404 แทน "auth failed"
- client คิดว่าไฟล์ไม่มี แต่จริง ๆ token หมดอายุ
- debug ยาก
3. centralize-api ทุก error เป็น 400
- DB error → 400 (ควรเป็น 500)
- client ไม่รู้ว่าเป็น validation หรือ server crash
4. Production masking อาจซ่อน root cause
- vtrc-api แปลง DB error เป็น
INTERNAL_SERVER_ERROR - ต้องดู log ฝั่ง server เพื่อหาสาเหตุจริง
5. cu-central-api auth error ไม่ได้เป็น HTTP status
- ส่ง 200 +
{ error: "AUTH_USER_OR_PASS_INVALID" } - client ต้องเช็ค
errorfield ไม่ใช่ status code
Checklist สำหรับ client
ถ้าเขียน client ใหม่ — ต้องจัดการ code เหล่านี้
- [ ]
TOKEN_EXPIRED→ refresh token + retry - [ ]
TOKEN_INVALID/SESSION_EXPIRED→ logout + redirect login - [ ]
ACCESS_KEY_INVALID→ แจ้ง admin แก้ config - [ ]
CANNOT_ACCESS→ แจ้ง user ไม่มีสิทธิ์ - [ ] Network error → แจ้ง "เครือข่ายมีปัญหา"
- [ ] 500 / INTERNAL_SERVER_ERROR → แจ้ง "เกิดข้อผิดพลาด ลองใหม่"
- [ ] REST 404 → เช็คก่อนว่า auth ผิด หรือไฟล์ไม่มีจริง