Skip to content
Our Sponsors
Open in Anthropic

OpenAPI

Elysia는 기본적으로 OpenAPI 스키마를 따르고 일급 지원을 제공합니다.

Elysia는 OpenAPI 플러그인을 사용하여 자동으로 API 문서 페이지를 생성할 수 있습니다.

Swagger 페이지를 생성하려면 플러그인을 설치하세요:

bash
bun add @elysiajs/openapi

그리고 플러그인을 서버에 등록하세요:

typescript
import { Elysia } from 'elysia'
import { openapi } from '@elysiajs/openapi'

new Elysia()
	.use(openapi()) 

기본적으로 Elysia는 OpenAPI V3 스키마와 Scalar UI를 사용합니다.

OpenAPI 플러그인 구성에 대해서는 OpenAPI 플러그인 페이지를 참조하세요.

타입에서 OpenAPI 생성

이는 선택 사항이지만 훨씬 더 나은 문서 경험을 위해 강력히 권장합니다.

기본적으로 Elysia는 런타임 스키마를 사용하여 OpenAPI 문서를 생성합니다.

그러나 다음과 같이 OpenAPI 플러그인의 생성기를 사용하여 타입에서 OpenAPI 문서를 생성할 수도 있습니다:

  1. Elysia 루트 파일을 지정하고(지정하지 않으면 Elysia는 src/index.ts를 사용합니다), 인스턴스를 내보냅니다

  2. 생성기를 가져오고 타입 생성기에 프로젝트 루트에서 파일 경로를 제공합니다

ts
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'

export const app = new Elysia() 
    .use(
        openapi({
            references: fromTypes() 
        })
    )
    .get('/', { test: 'hello' as const })
    .post('/json', ({ body, status }) => body, {
        body: t.Object({
            hello: t.String()
        })
    })
    .listen(3000)

Elysia는 내보낸 인스턴스의 타입을 읽어 OpenAPI 문서를 생성하려고 시도합니다.

이는 런타임 스키마와 공존하며 런타임 스키마가 타입 정의보다 우선합니다.

프로덕션

프로덕션 환경에서는 Elysia를 Bun으로 단일 실행 파일로 컴파일하거나 단일 JavaScript 파일로 번들링할 가능성이 높습니다.

생성기에 타입 선언을 제공하려면 선언 파일(.d.ts)을 미리 생성하는 것이 좋습니다.

ts
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'

const app = new Elysia()
    .use(
        openapi({
            references: fromTypes(
            	process.env.NODE_ENV === 'production'
             		? 'dist/index.d.ts'
               		: 'src/index.ts'
            )
        })
    )
타입 생성에 문제가 있나요?

주의사항: 루트 경로

프로젝트의 루트를 추측하는 것은 신뢰할 수 없으므로, 특히 모노레포를 사용할 때 생성기가 올바르게 실행되도록 프로젝트 루트 경로를 제공하는 것이 좋습니다.

ts
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'

export const app = new Elysia()
    .use(
        openapi({
            references: fromTypes('src/index.ts', {
            	projectRoot: path.join('..', import.meta.dir) 
            })
        })
    )
    .get('/', { test: 'hello' as const })
    .post('/json', ({ body, status }) => body, {
        body: t.Object({
            hello: t.String()
        })
    })
    .listen(3000)

사용자 정의 tsconfig.json

tsconfig.json 파일이 여러 개 있는 경우 타입 생성에 사용할 올바른 tsconfig.json 파일을 지정해야 합니다.

ts
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'

export const app = new Elysia()
    .use(
        openapi({
            references: fromTypes('src/index.ts', {
            	// This is reference from root of the project
            	tsconfigPath: 'tsconfig.dts.json'
            })
        })
    )
    .get('/', { test: 'hello' as const })
    .post('/json', ({ body, status }) => body, {
        body: t.Object({
            hello: t.String()
        })
    })
    .listen(3000)

OpenAPI와 Standard Schema

Elysia는 각 스키마의 네이티브 메서드를 사용하여 OpenAPI 스키마로 변환하려고 시도합니다.

그러나 스키마가 네이티브 메서드를 제공하지 않는 경우 다음과 같이 mapJsonSchema를 제공하여 OpenAPI에 대한 사용자 정의 스키마를 제공할 수 있습니다:

Zod OpenAPI

Zod는 스키마에 toJSONSchema 메서드가 없으므로 Zod 스키마를 OpenAPI 스키마로 변환하기 위한 사용자 정의 매퍼를 제공해야 합니다.

typescript
import openapi from '@elysiajs/openapi'
import * as z from 'zod'

openapi({
	mapJsonSchema: {
		zod: z.toJSONSchema
	}
})
typescript
import openapi from '@elysiajs/openapi'
import { zodToJsonSchema } from 'zod-to-json-schema'

openapi({
	mapJsonSchema: {
		zod: zodToJsonSchema
	}
})

라우트 설명

스키마 타입을 제공하여 라우트 정보를 추가할 수 있습니다.

그러나 때로는 타입만 정의해도 라우트가 무엇을 하는지 명확하지 않을 수 있습니다. detail 필드를 사용하여 라우트를 명시적으로 설명할 수 있습니다.

typescript
import { Elysia, t } from 'elysia'
import { openapi } from '@elysiajs/openapi'

new Elysia()
	.use(openapi())
	.post(
		'/sign-in',
		({ body }) => body, {
    		body: t.Object(
      		{
	            username: t.String(),
	            password: t.String({
	                minLength: 8,
	                description: 'User password (at least 8 characters)'
	            })
	        },
	        { 
	            description: 'Expected a username and password'
	        } 
	    ),
	    detail: { 
	        summary: 'Sign in the user', 
	        tags: ['authentication'] 
	    } 
	})

detail 필드는 기본적으로 자동 완성 및 타입 안전성을 갖춘 OpenAPI V3 정의를 따릅니다.

그런 다음 detail은 OpenAPI 라우트에 설명을 넣기 위해 OpenAPI에 전달됩니다.

응답 헤더

withHeader로 스키마를 래핑하여 응답 헤더를 추가할 수 있습니다:

typescript
import { Elysia, t } from 'elysia'
import { openapi, withHeader } from '@elysiajs/openapi'

new Elysia()
	.use(openapi())
	.get(
		'/thing',
		({ body, set }) => {
			set.headers['x-powered-by'] = 'Elysia'

			return body
		},
		{
		    response: withHeader( 
				t.Literal('Hi'), 
				{ 
					'x-powered-by': t.Literal('Elysia') 
				} 
			) 
		}
	)

withHeader는 주석 전용이며 실제 응답 헤더를 강제하거나 검증하지 않습니다. 헤더를 수동으로 설정해야 합니다.

라우트 숨기기

detail.hidetrue로 설정하여 Swagger 페이지에서 라우트를 숨길 수 있습니다.

typescript
import { Elysia, t } from 'elysia'
import { openapi } from '@elysiajs/openapi'

new Elysia()
	.use(openapi())
	.post(
		'/sign-in',
		({ body }) => body,
		{
		    body: t.Object(
		        {
		            username: t.String(),
		            password: t.String()
		        },
		        {
		            description: 'Expected a username and password'
		        }
		    ),
		    detail: { 
		        hide: true
		    } 
		}
	)

태그

Elysia는 Swagger의 태그 시스템을 사용하여 엔드포인트를 그룹으로 분리할 수 있습니다.

먼저 swagger 구성 객체에서 사용 가능한 태그를 정의하세요.

typescript
new Elysia().use(
    openapi({
        documentation: {
            tags: [
                { name: 'App', description: 'General endpoints' },
                { name: 'Auth', description: 'Authentication endpoints' }
            ]
        }
    })
)

그런 다음 엔드포인트 구성 섹션의 details 속성을 사용하여 해당 엔드포인트를 그룹에 할당합니다.

typescript
new Elysia()
    .get('/', () => 'Hello Elysia', {
        detail: {
            tags: ['App']
        }
    })
    .group('/auth', (app) =>
        app.post(
            '/sign-up',
            ({ body }) =>
                db.user.create({
                    data: body,
                    select: {
                        id: true,
                        username: true
                    }
                }),
            {
                detail: {
                    tags: ['Auth']
                }
            }
        )
    )

다음과 같은 swagger 페이지가 생성됩니다. image

태그 그룹

Elysia는 전체 인스턴스 또는 라우트 그룹을 특정 태그에 추가하기 위해 태그를 허용할 수 있습니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia({
    tags: ['user']
})
    .get('/user', 'user')
    .get('/admin', 'admin')

모델

참조 모델을 사용하면 Elysia가 스키마 생성을 자동으로 처리합니다.

모델을 전용 섹션으로 분리하고 참조로 연결합니다.

typescript
new Elysia()
    .model({
        User: t.Object({
            id: t.Number(),
            username: t.String()
        })
    })
    .get('/user', () => ({ id: 1, username: 'saltyaom' }), {
        response: {
            200: 'User'
        },
        detail: {
            tags: ['User']
        }
    })

Guard

또는 Elysia는 전체 인스턴스 또는 라우트 그룹을 특정 guard에 추가하기 위해 guard를 허용할 수 있습니다.

typescript
import { Elysia, t } from 'elysia'

new Elysia()
    .guard({
        detail: {
            description: 'Require user to be logged in'
        }
    })
    .get('/user', 'user')
    .get('/admin', 'admin')

OpenAPI 엔드포인트 변경

플러그인 구성에서 path를 설정하여 OpenAPI 엔드포인트를 변경할 수 있습니다.

typescript
import { 
Elysia
} from 'elysia'
import {
openapi
} from '@elysiajs/openapi'
new
Elysia
()
.
use
(
openapi
({
path
: '/v2/openapi'
}) ) .
listen
(3000)

OpenAPI 정보 사용자 정의

플러그인 구성에서 documentation.info를 설정하여 OpenAPI 정보를 사용자 정의할 수 있습니다.

typescript
import { 
Elysia
} from 'elysia'
import {
openapi
} from '@elysiajs/openapi'
new
Elysia
()
.
use
(
openapi
({
documentation
: {
info
: {
title
: 'Elysia Documentation',
version
: '1.0.0'
} } }) ) .
listen
(3000)

이는 다음에 유용할 수 있습니다:

  • 제목 추가
  • API 버전 설정
  • API가 무엇에 관한 것인지 설명하는 설명 추가
  • 사용 가능한 태그, 각 태그의 의미 설명

보안 구성

API 엔드포인트를 보호하려면 Swagger 구성에서 보안 스키마를 정의할 수 있습니다. 아래 예제는 Bearer Authentication(JWT)을 사용하여 엔드포인트를 보호하는 방법을 보여줍니다:

typescript
new Elysia().use(
    openapi({
        documentation: {
            components: {
                securitySchemes: {
                    bearerAuth: {
                        type: 'http',
                        scheme: 'bearer',
                        bearerFormat: 'JWT'
                    }
                }
            }
        }
    })
)

export const addressController = new Elysia({
    prefix: '/address',
    detail: {
        tags: ['Address'],
        security: [
            {
                bearerAuth: []
            }
        ]
    }
})

이렇게 하면 /address 접두사 아래의 모든 엔드포인트에 액세스하려면 유효한 JWT 토큰이 필요합니다.