Skip to content
Our Sponsors
Open in Anthropic

Validation ​

API 서버를 만드는 목적은 입력을 받아 처리하는 것입니다.

JavaScript는 모든 데이터가 모든 타입이 될 수 있도록 허용합니다. Elysia는 데이터가 올바른 형식인지 확인하기 위해 데이터를 검증하는 도구를 기본적으로 제공합니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/id/:id', ({
params
: {
id
} }) =>
id
, {
params
:
t
.
Object
({
id
:
t
.
Number
()
}) }) .
listen
(3000)

TypeBox ​

Elysia.t는 런타임, 컴파일 타임 및 단일 소스의 진실에서 OpenAPI 스키마 생성에서 타입 안전성을 제공하는 TypeBox 기반 스키마 빌더입니다.

Elysia는 원활한 경험을 위해 서버 측 유효성 검사에 TypeBox를 맞춤화합니다.

Standard Schema ​

Elysia는 Standard Schema도 지원하여 선호하는 유효성 검사 라이브러리를 사용할 수 있습니다:

  • Zod
  • Valibot
  • ArkType
  • Effect Schema
  • Yup
  • Joi
  • 그 외

Standard Schema를 사용하려면 스키마를 가져와서 경로 핸들러에 제공하기만 하면 됩니다.

typescript
import { 
Elysia
} from 'elysia'
import {
z
} from 'zod'
import * as
v
from 'valibot'
new
Elysia
()
.
get
('/id/:id', ({
params
: {
id
},
query
: {
name
} }) =>
id
, {
params
:
z
.
object
({
id
:
z
.
coerce
.
number
()
}),
query
:
v
.
object
({
name
:
v
.
literal
('Lilith')
}) }) .
listen
(3000)

문제 없이 동일한 핸들러에서 모든 검증기를 함께 사용할 수 있습니다.

Schema type ​

Elysia는 다음 타입으로 선언적 스키마를 지원합니다:


이러한 속성은 들어오는 요청의 유효성을 검사하기 위해 경로 핸들러의 세 번째 인수로 제공되어야 합니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
    .get('/id/:id', () => 'Hello World!', {
        query: t.Object({
            name: t.String()
        }),
        params: t.Object({
            id: t.Number()
        })
    })
    .listen(3000)
localhost

GET

응답은 다음과 같아야 합니다:

URLQueryParams
/id/a❌❌
/id/1?name=Elysia✅✅
/id/1?alias=Elysia❌✅
/id/a?name=Elysia✅❌
/id/a?alias=Elysia❌❌

스키마가 제공되면 타입이 스키마에서 자동으로 추론되고 API 문서에 대한 OpenAPI 타입이 생성되어 수동으로 타입을 제공하는 중복 작업을 제거합니다.

Guard ​

Guard를 사용하여 여러 핸들러에 스키마를 적용할 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/none', ({
query
}) => 'hi')
.
guard
({
query
:
t
.
Object
({
name
:
t
.
String
()
}) }) .
get
('/query', ({
query
}) =>
query
)
.
listen
(3000)

이 코드는 쿼리가 그 이후의 모든 핸들러에 대해 문자열 값을 가진 name을 가져야 함을 보장합니다. 응답은 다음과 같이 나열되어야 합니다:

localhost

GET

응답은 다음과 같이 나열되어야 합니다:

PathResponse
/nonehi
/none?name=ahi
/queryerror
/query?name=aa

동일한 속성에 대해 여러 전역 스키마가 정의된 경우 최신 스키마가 우선합니다. 로컬 및 전역 스키마가 모두 정의된 경우 로컬 스키마가 우선합니다.

Guard Schema Type ​

Guard는 유효성 검사를 정의하는 2가지 타입을 지원합니다.

override (기본값) ​

스키마가 서로 충돌하는 경우 스키마를 재정의합니다.

Elysia run with default override guard showing schema gets override

standalone ​

충돌하는 스키마를 분리하고 둘 다 독립적으로 실행하여 둘 다 유효성을 검사합니다.

Elysia run with standalone merging multiple guard together

schema로 guard의 스키마 타입을 정의하려면:

ts
import { Elysia } from 'elysia'

new Elysia()
	.guard({
		schema: 'standalone', 
		response: t.Object({
			title: t.String()
		})
	})

Body ​

들어오는 HTTP 메시지는 서버로 전송되는 데이터입니다. JSON, form-data 또는 다른 형식일 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
post
('/body', ({
body
}) =>
body
, {
body
:
t
.
Object
({
name
:
t
.
String
()
}) }) .
listen
(3000)

유효성 검사는 다음과 같아야 합니다:

BodyValidation
{ name: 'Elysia' }✅
{ name: 1 }❌
{ alias: 'Elysia' }❌
undefined❌

Elysia는 HTTP/1.1 RFC2616의 사양에 따라 GET 및 HEAD 메시지에 대한 body-parser를 기본적으로 비활성화합니다.

요청 메서드에 entity-body에 대한 정의된 의미 체계가 포함되지 않은 경우 요청을 처리할 때 message-body를 무시해야 합니다(SHOULD).

대부분의 브라우저는 기본적으로 GET 및 HEAD 메서드에 대한 body의 첨부를 비활성화합니다.

Specs ​

들어오는 HTTP 메시지 (또는 body)의 유효성을 검사합니다.

이러한 메시지는 웹 서버가 처리할 추가 메시지입니다.

body는 fetch API의 body와 같은 방식으로 제공됩니다. 콘텐츠 타입은 정의된 body에 따라 적절하게 설정되어야 합니다.

typescript
fetch('https://elysiajs.com', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        name: 'Elysia'
    })
})

File ​

File은 파일을 업로드하는 데 사용할 수 있는 특수한 타입의 body입니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
post
('/body', ({
body
}) =>
body
, {
body
:
t
.
Object
({
file
:
t
.
File
({
format
: 'image/*' }),
multipleFiles
:
t
.
Files
()
}) }) .
listen
(3000)

파일 타입을 제공하면 Elysia는 자동으로 content-type이 multipart/form-data라고 가정합니다.

File (Standard Schema) ​

Standard Schema를 사용하는 경우 Elysia가 t.File과 유사하게 콘텐츠 타입을 자동으로 검증할 수 없다는 점이 중요합니다.

그러나 Elysia는 매직 넘버를 사용하여 파일 타입을 검증하는 데 사용할 수 있는 fileType을 내보냅니다.

typescript
import { 
Elysia
,
fileType
} from 'elysia'
import {
z
} from 'zod'
new
Elysia
()
.
post
('/body', ({
body
}) =>
body
, {
body
:
z
.
object
({
file
:
z
.
file
().
refine
((
file
) =>
fileType
(
file
, 'image/jpeg'))
}) })

대부분의 검증기가 실제로 파일을 올바르게 검증하지 않기 때문에 fileType을 사용하여 파일 타입을 검증해야 합니다. 콘텐츠 타입의 값을 확인하는 것과 같이 보안 취약점으로 이어질 수 있습니다.

Query ​

Query는 URL을 통해 전송되는 데이터입니다. ?key=value 형식일 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/query', ({
query
}) =>
query
, {
query
:
t
.
Object
({
name
:
t
.
String
()
}) }) .
listen
(3000)

Query는 객체 형식으로 제공되어야 합니다.

유효성 검사는 다음과 같아야 합니다:

QueryValidation
/?name=Elysia✅
/?name=1✅
/?alias=Elysia❌
/?name=ElysiaJS&alias=Elysia✅
/❌

Specs ​

쿼리 문자열은 ?로 시작하는 URL의 일부이며, 일반적으로 필터링이나 검색과 같은 사용자 정의 동작을 위해 서버에 추가 정보를 전달하는 데 사용되는 키-값 쌍인 하나 이상의 쿼리 매개변수를 포함할 수 있습니다.

URL Object

Query는 Fetch API에서 ? 뒤에 제공됩니다.

typescript
fetch('https://elysiajs.com/?name=Elysia')

쿼리 매개변수를 지정할 때 모든 쿼리 매개변수 값은 문자열로 표현되어야 한다는 점을 이해하는 것이 중요합니다. 이는 URL에 인코딩되고 추가되는 방식 때문입니다.

Coercion ​

Elysia는 query에서 적용 가능한 스키마를 각각의 타입으로 자동으로 강제 변환합니다.

자세한 내용은 Elysia behavior를 참조하세요.

ts
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/', ({
query
}) =>
query
, {
query
:
t
.
Object
({
name
:
t
.
Number
()
}) }) .
listen
(3000)
localhost

GET

1

Array ​

기본적으로 Elysia는 여러 번 지정되더라도 쿼리 매개변수를 단일 문자열로 취급합니다.

배열을 사용하려면 명시적으로 배열로 선언해야 합니다.

ts
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/', ({
query
}) =>
query
, {
query
:
t
.
Object
({
name
:
t
.
Array
(
t
.
String
())
}) }) .
listen
(3000)
localhost

GET

{ "name": [ "rapi", "anis", "neon" ], "squad": "counter" }

Elysia가 속성이 배열에 할당 가능한 것을 감지하면 Elysia는 지정된 타입의 배열로 강제 변환합니다.

기본적으로 Elysia는 다음 형식으로 쿼리 배열을 포맷합니다:

nuqs ​

이 형식은 nuqs에서 사용됩니다.

,를 구분 기호로 사용하면 속성이 배열로 취급됩니다.

http://localhost?name=rapi,anis,neon&squad=counter
{
	name: ['rapi', 'anis', 'neon'],
	squad: 'counter'
}

HTML form format ​

키가 여러 번 할당되면 키는 배열로 취급됩니다.

이는 같은 이름을 가진 입력이 여러 번 지정될 때 HTML 양식 형식과 유사합니다.

http://localhost?name=rapi&name=anis&name=neon&squad=counter
// name: ['rapi', 'anis', 'neon']

Params ​

Params 또는 경로 매개변수는 URL 경로를 통해 전송되는 데이터입니다.

/key 형식일 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/id/:id', ({
params
}) =>
params
, {
params
:
t
.
Object
({
id
:
t
.
Number
()
}) })
localhost

GET

Params는 객체 형식으로 제공되어야 합니다.

유효성 검사는 다음과 같아야 합니다:

URLValidation
/id/1✅
/id/a❌

Specs ​

경로 매개변수 (쿼리 문자열 또는 쿼리 매개변수와 혼동하지 말 것).

특정 값 패턴(예: 숫자 값 또는 템플릿 리터럴 패턴)이 필요한 경우가 아니면 Elysia가 경로 매개변수에서 자동으로 타입을 추론할 수 있으므로 이 필드는 일반적으로 필요하지 않습니다.

typescript
fetch('https://elysiajs.com/id/1')

Params type inference ​

params 스키마가 제공되지 않으면 Elysia는 자동으로 타입을 문자열로 추론합니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/id/:id', ({
params
}) =>
params
)

Headers ​

Headers는 요청 헤더를 통해 전송되는 데이터입니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/headers', ({
headers
}) =>
headers
, {
headers
:
t
.
Object
({
authorization
:
t
.
String
()
}) })

다른 타입과 달리 헤더는 기본적으로 additionalProperties가 true로 설정됩니다.

즉, 헤더는 모든 키-값 쌍을 가질 수 있지만 값은 스키마와 일치해야 합니다.

Specs ​

HTTP 헤더를 사용하면 클라이언트와 서버가 HTTP 요청 또는 응답과 함께 추가 정보를 전달할 수 있으며 일반적으로 메타데이터로 취급됩니다.

이 필드는 일반적으로 특정 헤더 필드(예: Authorization)를 강제하는 데 사용됩니다.

Headers는 fetch API의 body와 같은 방식으로 제공됩니다.

typescript
fetch('https://elysiajs.com/', {
    headers: {
        authorization: 'Bearer 12345'
    }
})

TIP

Elysia는 헤더를 소문자 키로만 파싱합니다.

헤더 유효성 검사를 사용할 때 소문자 필드 이름을 사용하고 있는지 확인하세요.

Cookie는 요청 쿠키를 통해 전송되는 데이터입니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/cookie', ({
cookie
}) =>
cookie
, {
cookie
:
t
.
Cookie
({
cookieName
:
t
.
String
()
}) })

Cookies는 t.Cookie 또는 t.Object 형식으로 제공되어야 합니다.

headers와 마찬가지로 cookies는 기본적으로 additionalProperties가 true로 설정됩니다.

Specs ​

HTTP 쿠키는 서버가 클라이언트에게 보내는 작은 데이터 조각입니다. 동일한 웹 서버를 방문할 때마다 전송되는 데이터로, 서버가 클라이언트 정보를 기억할 수 있도록 합니다.

간단히 말해서 모든 요청과 함께 전송되는 문자열화된 상태입니다.

이 필드는 일반적으로 특정 쿠키 필드를 강제하는 데 사용됩니다.

쿠키는 Fetch API가 사용자 정의 값을 허용하지 않는 특수한 헤더 필드이지만 브라우저에서 관리됩니다. 쿠키를 보내려면 credentials 필드를 사용해야 합니다:

typescript
fetch('https://elysiajs.com/', {
    credentials: 'include'
})

t.Cookie는 t.Object와 동일하지만 쿠키별 옵션을 설정할 수 있는 특수 타입입니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
get
('/cookie', ({
cookie
}) =>
cookie
.
name
.
value
, {
cookie
:
t
.
Cookie
({
name
:
t
.
String
()
}, {
secure
: true,
httpOnly
: true
}) })

Response ​

Response는 핸들러에서 반환되는 데이터입니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
	.get('/response', () => {
		return {
			name: 'Jane Doe'
		}
	}, {
		response: t.Object({
			name: t.String()
		})
	})

Response per status ​

응답은 상태 코드별로 설정할 수 있습니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
	.get('/response', ({ status }) => {
		if (Math.random() > 0.5)
			return status(400, {
				error: 'Something went wrong'
			})

		return {
			name: 'Jane Doe'
		}
	}, {
		response: {
			200: t.Object({
				name: t.String()
			}),
			400: t.Object({
				error: t.String()
			})
		}
	})

이것은 Elysia 전용 기능으로 필드를 선택적으로 만들 수 있습니다.

Error Provider ​

유효성 검사가 실패할 때 사용자 정의 오류 메시지를 제공하는 두 가지 방법이 있습니다:

  1. 인라인 status 속성
  2. onError 이벤트 사용

Error Property ​

Elysia는 추가 error 속성을 제공하여 필드가 유효하지 않은 경우 사용자 정의 오류 메시지를 반환할 수 있습니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
    .post('/', () => 'Hello World!', {
        body: t.Object({
            x: t.Number({
               	error: 'x must be a number'
            })
        })
    })
    .listen(3000)

다음은 다양한 타입에서 error 속성을 사용하는 예입니다:

TypeBoxError
typescript
t.String({
    format: 'email',
    error: 'Invalid email :('
})
Invalid Email :(
typescript
t.Array(
    t.String(),
    {
        error: 'All members must be a string'
    }
)
All members must be a string
typescript
t.Object({
    x: t.Number()
}, {
    error: 'Invalid object UnU'
})
Invalid object UnU
typescript
t.Object({
    x: t.Number({
        error({ errors, type, validation, value }) {
            return 'Expected x to be a number'
        }
    })
})
Expected x to be a number

Custom Error ​

TypeBox는 추가 "error" 속성을 제공하여 필드가 유효하지 않은 경우 사용자 정의 오류 메시지를 반환할 수 있습니다.

TypeBoxError
typescript
t.String({
    format: 'email',
    error: 'Invalid email :('
})
Invalid Email :(
typescript
t.Object({
    x: t.Number()
}, {
    error: 'Invalid object UnU'
})
Invalid object UnU

Error message as function ​

문자열 외에도 Elysia 타입의 error는 함수를 받아 각 속성에 대한 사용자 정의 오류를 프로그래밍 방식으로 반환할 수 있습니다.

error 함수는 ValidationError와 동일한 인수를 받습니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
    .post('/', () => 'Hello World!', {
        body: t.Object({
            x: t.Number({
                error() {
                    return 'Expected x to be a number'
                }
            })
        })
    })
    .listen(3000)

TIP

error 위로 마우스를 가져가면 타입을 볼 수 있습니다.

Error is Called Per Field ​

error 함수는 필드가 유효하지 않은 경우에만 호출됩니다.

다음 표를 고려하세요:

CodeBodyError
typescript
t.Object({
    x: t.Number({
        error() {
            return 'Expected x to be a number'
        }
    })
})
json
{
    x: "hello"
}
Expected x to be a number
typescript
t.Object({
    x: t.Number({
        error() {
            return 'Expected x to be a number'
        }
    })
})
json
"hello"
(default error, `t.Number.error` is not called)
typescript
t.Object(
    {
        x: t.Number({
            error() {
                return 'Expected x to be a number'
            }
        })
    }, {
        error() {
            return 'Expected value to be an object'
        }
    }
)
json
"hello"
Expected value to be an object

onError ​

오류 코드를 "VALIDATION"으로 좁혀서 onError 이벤트를 기반으로 유효성 검사의 동작을 사용자 정의할 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
onError
(({
code
,
error
}) => {
if (
code
=== 'VALIDATION')
return
error
.
message
}) .
listen
(3000)

좁혀진 오류 타입은 elysia/error에서 가져온 ValidationError로 타입이 지정됩니다.

ValidationError는 TypeCheck로 타입이 지정된 validator라는 속성을 노출하여 TypeBox 기능을 기본적으로 상호 작용할 수 있습니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
    .onError(({ code, error }) => {
        if (code === 'VALIDATION')
            return error.all[0].message
    })
    .listen(3000)

Error List ​

ValidationError는 모든 오류 원인을 나열할 수 있는 ValidatorError.all 메서드를 제공합니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
	.post('/', ({ body }) => body, {
		body: t.Object({
			name: t.String(),
			age: t.Number()
		}),
		error({ code, error }) {
			switch (code) {
				case 'VALIDATION':
                    console.log(error.all)

                    // Find a specific error name (path is OpenAPI Schema compliance)
                    const name = error.all.find(
						(x) => x.summary && x.path === '/name'
					)

                    // If there is a validation error, then log it
                    if(name)
    					console.log(name)
			}
		}
	})
	.listen(3000)

TypeBox의 검증기에 대한 자세한 내용은 TypeCheck을 참조하세요.

Reference Model ​

때때로 중복 모델을 선언하거나 같은 모델을 여러 번 재사용하는 경우가 있을 수 있습니다.

참조 모델을 사용하면 모델에 이름을 지정하고 이름을 참조하여 재사용할 수 있습니다.

간단한 시나리오로 시작해보겠습니다.

동일한 모델로 로그인을 처리하는 컨트롤러가 있다고 가정합니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
const
app
= new
Elysia
()
.
post
('/sign-in', ({
body
}) =>
body
, {
body
:
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}),
response
:
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}) })

모델을 변수로 추출하고 참조하여 코드를 리팩토링할 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
// Maybe in a different file eg. models.ts const
SignDTO
=
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}) const
app
= new
Elysia
()
.
post
('/sign-in', ({
body
}) =>
body
, {
body
:
SignDTO
,
response
:
SignDTO
})

관심사를 분리하는 이 방법은 효과적인 접근 방식이지만 앱이 더 복잡해짐에 따라 여러 컨트롤러에서 여러 모델을 재사용하는 경우가 있을 수 있습니다.

모델을 "참조 모델"로 만들어 이를 해결할 수 있습니다. 이를 통해 모델에 이름을 지정하고 model로 모델을 등록하여 schema에서 직접 자동 완성을 사용하여 참조할 수 있습니다.

typescript
import { 
Elysia
,
t
} from 'elysia'
const
app
= new
Elysia
()
.
model
({
sign
:
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}) }) .
post
('/sign-in', ({
body
}) =>
body
, {
// with auto-completion for existing model name
body
: 'sign',
response
: 'sign'
})

모델 그룹에 액세스하려면 model을 플러그인으로 분리할 수 있습니다. 등록하면 여러 가져오기 대신 모델 세트를 제공합니다.

typescript
// auth.model.ts
import { Elysia, t } from 'elysia'

export const authModel = new Elysia()
    .model({
        sign: t.Object({
            username: t.String(),
            password: t.String()
        })
    })

그런 다음 인스턴스 파일에서:

typescript
// index.ts
import { 
Elysia
} from 'elysia'
import {
authModel
} from './auth.model'
const
app
= new
Elysia
()
.
use
(
authModel
)
.
post
('/sign-in', ({
body
}) =>
body
, {
// with auto-completion for existing model name
body
: 'sign',
response
: 'sign'
})

이 접근 방식은 관심사를 분리할 수 있을 뿐만 아니라 여러 곳에서 모델을 재사용하면서 모델을 OpenAPI 문서에 통합할 수 있습니다.

Multiple Models ​

model은 키가 모델 이름이고 값이 모델 정의인 객체를 받습니다. 여러 모델이 기본적으로 지원됩니다.

typescript
// auth.model.ts
import { Elysia, t } from 'elysia'

export const authModel = new Elysia()
    .model({
        number: t.Number(),
        sign: t.Object({
            username: t.String(),
            password: t.String()
        })
    })

Naming Convention ​

중복 모델 이름은 Elysia가 오류를 발생시킵니다. 중복 모델 이름 선언을 방지하려면 다음 명명 규칙을 사용할 수 있습니다.

모든 모델이 models/<name>.ts에 저장되어 있고 모델의 접두사를 네임스페이스로 선언한다고 가정해보겠습니다.

typescript
import { Elysia, t } from 'elysia'

// admin.model.ts
export const adminModels = new Elysia()
    .model({
        'admin.auth': t.Object({
            username: t.String(),
            password: t.String()
        })
    })

// user.model.ts
export const userModels = new Elysia()
    .model({
        'user.auth': t.Object({
            username: t.String(),
            password: t.String()
        })
    })

이것은 어느 정도 명명 중복을 방지할 수 있지만 궁극적으로는 팀이 명명 규칙을 결정하는 것이 가장 좋습니다.

Elysia는 결정 피로를 방지하는 데 도움이 되는 의견이 있는 옵션을 제공합니다.

TypeScript ​

다음과 같이 static 속성에 액세스하여 모든 Elysia/TypeBox 타입의 타입 정의를 가져올 수 있습니다:

ts
import { 
t
} from 'elysia'
const
MyType
=
t
.
Object
({
hello
:
t
.
Literal
('Elysia')
}) type
MyType
= typeof
MyType
.
static



이를 통해 Elysia는 타입을 추론하고 자동으로 제공하여 중복 스키마를 선언할 필요를 줄입니다.

단일 Elysia/TypeBox 스키마는 다음에 사용될 수 있습니다:

  • 런타임 유효성 검사
  • 데이터 강제 변환
  • TypeScript 타입
  • OpenAPI 스키마

이를 통해 스키마를 단일 소스의 진실로 만들 수 있습니다.