Schema
Schema is used to define the strict type for the Elysia handler.
Schema is not an event but a value used in a validation event to strictly type and validate an incoming request and outgoing response.
The schema consists of:
- body - validate incoming body.
- query - validate query string or URL parameters.
- params - validate path parameters.
- header - validate request's headers.
- response - validate response type.
- detail - Explicitly define what can route does, see (creating documentation) for more explanation.
Schema is defined as:
- Locally: in a handler
- Globally: limits to the scope
Local Schema
Local schema tied to a specific route in a local handler.
To define a schema, import t
, a schema builder re-exported from @sinclair/typebox
:
import { Elysia, t } from 'elysia'
new Elysia()
.post('/mirror', ({ body }) => body, {
body: t.Object({
username: t.String(),
password: t.String()
})
})
import { Elysia, t } from 'elysia'
new Elysia()
.post('/mirror', ({ body }) => body, {
body: t.Object({
username: t.String(),
password: t.String()
})
})
This will strictly validate the incoming body and infer body type in the handler as:
// Inferred type
interface Body {
username: string
password: string
}
// Inferred type
interface Body {
username: string
password: string
}
This means that you will get strict type defining once from a schema and get inferred type to TypeScript without needing to write a single TypeScript.
Global and scope
The global schema will define all types of a handler in the scope.
app.guard({
response: t.String()
}, app => app
.get('/', () => 'Hi')
// Invalid: will throw error
.get('/invalid', () => 1)
)
app.guard({
response: t.String()
}, app => app
.get('/', () => 'Hi')
// Invalid: will throw error
.get('/invalid', () => 1)
)
The global type will be overwritten by the nearest schema to the handler.
In another word, inherits schema is rewritten by the inner scope.
app.guard({
response: t.String()
}, app => app.guard({
response: t.Number()
}, app => app
// Invalid: will throw an error
.get('/', () => 'Hi')
.get('/this-is-now-valid', () => 1)
)
)
app.guard({
response: t.String()
}, app => app.guard({
response: t.Number()
}, app => app
// Invalid: will throw an error
.get('/', () => 'Hi')
.get('/this-is-now-valid', () => 1)
)
)
group
and guard
will define the scope limit, so the type will not get out of the scope handler.
Multiple Status Response
By default schema.response
can accept either a schema definition or a map or stringified status code with schema.
Allowing the Elysia server to define a type for each response status.
import { Elysia, t } from 'elysia'
new Elysia()
.post('/', ({ body }) => doSomething(), {
response: {
200: t.Object({
username: t.String(),
password: t.String()
}),
400: t.String()
}
})
import { Elysia, t } from 'elysia'
new Elysia()
.post('/', ({ body }) => doSomething(), {
response: {
200: t.Object({
username: t.String(),
password: t.String()
}),
400: t.String()
}
})
Reference Models
Sometime you might find yourself re-use the same type multiple time.
Using reference models, you can named your model and use it by referencing the name:
// auth.model.ts
import { Elysia } from 'elysia'
// index.ts
import { Elysia } from 'elysia'
import { authModel } from './auth.model.ts'
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'
})
// auth.model.ts
import { Elysia } from 'elysia'
// index.ts
import { Elysia } from 'elysia'
import { authModel } from './auth.model.ts'
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'
})
For more explaination, see Reference Models.