Skip to content

11.7 · Error model — 3 services เทียบกัน

บทนี้รวบรวม error code, error response shape, และ HTTP status ของทั้ง 3 services ไว้ในที่เดียว เพื่อให้ frontend และ client อื่น ๆ จัดการ error ได้ถูกต้อง


ภาพรวม — แต่ละ service จัดการ error อย่างไร

ServiceError formatที่เก็บ codeHTTP status
vtrc-api (GraphQL){ errors: [{ message, extensions: { code } }] }lib/responErrors.js:31-229n/a (GraphQL ใช้ 200 เสมอ)
vtrc-api (REST)ไม่มี envelope — sendStatus(404/400/500)inline200/400/404/500
cu-central-api (GraphQL){ errors: [{ message, extensions: { code } }] }lib/responseAndLog.js:55-128n/a
cu-central-api (REST){ error: code, errorText: "...", errorVariable: {}, responsedAt: "..." }lib/auth/auth.js:292-301200/400
centralize-api{ statusCode: 400, message: "..." }libs/exception/http-exception.filter.ts200/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

CodeMessageเมื่อไร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 sessionlogout ที่อื่น
AUTH_USER_OR_PASS_INVALID(custom msg)credentials ผิดแจ้ง user
AUTH_LIMIT"ระบุเกินจำนวนครั้งที่กำหนด..."login ผิดเกินจำกัดรอ + ลองใหม่
CANNOT_ACCESS"คุณไม่มีสิทธิ์ในการเข้าถึง"role/menu ผิดแจ้ง admin
UNAUTHENTICATED(จาก cu-central-api)forward ได้ UNAUTHENTICATEDmapped เป็น TOKEN_INVALID
INTERNAL_SERVER_ERROR"บางสิ่งทำงานไม่ถูกต้อง..."fallbackลองใหม่

Error code catalog — อื่น ๆ

Code groupตัวอย่าง
Centralize (forward)16 codes (CENTRALIZE_*)
3rd party (Conicle)3 codes
Study leaveMSG001MSG015 (Thai messages)
GraphQL validationGRAPHQL_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 / ApolloError

Error code catalog — ~40 codes

GroupCodes
ServerINTERNAL_SERVER_ERROR
FormatFORMAT_DATE_INVALID, MONTH_INVALID, YEAR_INVALID
ProfilePROFILE_NOT_EXISTS, PROFILE_NOT_FOUND, PROFILE_NOT_COMPLETE, EMPLOYEE_NOT_FOUND
AccountACCOUNT_NOT_EXISTS, ACCOUNT_INVALID
PasswordPASSWORD_* (8 codes) — PASSWORD_TOO_SHORT, PASSWORD_NOT_MATCH, ...
EmailEMAIL_NOT_EXISTS, EMAIL_INVALID_*
PhonePHONE_NUMBER_*
SMSSMS_*
OTPOTP_*
VerifyVERIFY_INVALID, VERIFY_CODE_* (3 codes)
PayrollPAYROLL_SLIP_NOT_EXISTS
DocumentDOCUMENT_NOT_FOUND
DataDATA_INVALID, DATA_NOT_FOUND
CustomMSG014, 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

CodeText
AUTH_REQUIRED_PARAMSRequired some parameter.
AUTH_USER_OR_PASS_INVALIDUsername or Password is invalid.
AUTH_USER_NOT_ALLOWEDThis user is not allowed.
REFRESH_TOKEN_MIS_MATCHRefresh token mismatch.
ACCESS_TOKEN_INVALIDAccess token is invalid.
AUTH_API_KEY_INVALIDAPI 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 รวมอยู่ด้วย)
401JWT fail (ไม่เกิดเพราะ @Public())

จุดผิดปกติ

  • DB error → 400 — ควรเป็น 500 แต่ catch block แปลงเป็น BadRequestException หมด
  • ไม่มี 404 — query ไม่เจอ return [] ไม่ throw
  • ไม่มี 403, 409 — ไม่ได้ใช้

เปรียบเทียบ 3 services — error code overlap

Conceptvtrc-apicu-central-apicentralize-api
Auth failTOKEN_INVALID, TOKEN_EXPIRED, SESSION_*ACCESS_TOKEN_INVALID, AUTH_*401 (ไม่เกิด)
apiKey ผิดACCESS_KEY_INVALIDAUTH_API_KEY_INVALIDn/a
Not found(varies)*_NOT_FOUND, *_NOT_EXISTSไม่มี (return [])
ValidationUserInputError + customFORMAT_*_INVALID, DATA_INVALIDclass-validator auto → 400
Server errorINTERNAL_SERVER_ERRORINTERNAL_SERVER_ERROR400 (แปลงจาก 500)

Frontend จัดการ error อย่างไร

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 ต้องเช็ค error field ไม่ใช่ 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 ผิด หรือไฟล์ไม่มีจริง

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

ไป บท 10.8 File upload & download + PDF generation