Schemas
Galbe provides a custom Schema Type processor that offers type safety, data parsing, and validation. Using Schemas greatly simplifies request input validation and automatic error handling. It also enhances the developer experience by inferring static TypeScript types from schema definitions.
Schema Types
To use Schema definitions, import $T from the galbe library:
import { $T } from 'galbe' Every schema type accepts an optional options object as its last argument. The following keys are common to all types:
- id (
string) — A unique identifier for the schema. - title (
string) — A human-readable title. - description (
string) — A description of the schema. - default (
any) — A default value used when the input is omitted. - example / examples (
any) — Example value(s), surfaced by spec generators (e.g. OpenAPI).
Type-specific options are listed alongside each type below.
Here is the list of available Schema types in Galbe:
boolean
Schema Type matching boolean values.
const boolSchema = $T.boolean() string
Schema Type matching string values.
const strSchema = $T.string({ minLength: 1, maxLength: 64, pattern: /^[a-z]+$/, format: 'email' }) Options:
- minLength (
number) — Minimum string length. - maxLength (
number) — Maximum string length. - pattern (
RegExp) — A regular expression the value must match. - format (
string) — A semantic format hint (e.g.email,uuid), surfaced by spec generators.
number
Schema Type matching number values.
const numSchema = $T.number({ min: 0, max: 10, exclusiveMin: 0, exclusiveMax: 10 }) Options: min, max, exclusiveMin, exclusiveMax.
integer
Schema Type matching integer number values.
const intSchema = $T.integer({ min: 0, max: 10, exclusiveMin: 0, exclusiveMax: 10 }) Options: same as number.
null
Schema Type matching null values.
const nullSchema = $T.null() literal
Schema Type matching a single literal string, number, or boolean value.
const litSchema = $T.literal('admin') byteArray
Schema Type matching binary content as a Uint8Array.
const baSchema = $T.byteArray({ minLength: 0, maxLength: 1024 }) Options: minLength, maxLength (in bytes).
any
Schema Type matching any value.
const anySchema = $T.any() array
Schema Type matching array values.
const arraySchema = $T.array($T.any(), { minLength: 1, maxLength: 5, unique: true }) Options:
- minLength (
number) — Minimum number of items. - maxLength (
number) — Maximum number of items. - unique (
boolean) — Whentrue, all items must be unique.
object
Schema Type matching object values with typed properties.
const objSchema = $T.object({
name: $T.string(),
age: $T.optional($T.integer({ min: 0 }))
}) multipartForm
Schema Type for multipart/form-data request bodies. Each property describes a form part.
const formSchema = $T.multipartForm({
username: $T.string(),
avatar: $T.byteArray()
}) json
Wraps a primitive or object schema and tags it as JSON content. Useful for typing nested JSON payloads inside other schemas (e.g. a JSON-typed multipart/form-data part).
const jsonSchema = $T.json($T.object({ id: $T.string() })) optional
Makes any type optional, allowing undefined values.
const optionalSchema = $T.optional($T.string()) nullable
Makes any type nullable, allowing null values. Equivalent to a union with null.
const nullableSchema = $T.nullable($T.string()) nullish
Makes any type nullish, allowing both undefined and null values.
const nullishSchema = $T.nullish($T.string()) union
Creates a union of Schema Types.
const unionSchema = $T.union([$T.string(), $T.number()]) intersection
Creates an intersection of Schema Types. Members must be objects, unions, or other intersections.
const intersectionSchema = $T.intersection([
$T.object({ a: $T.string() }),
$T.object({ b: $T.number() })
]) stream
Wraps a streamable schema (byteArray, string, multipartForm, object, union, intersection) so that the request body is exposed as an AsyncGenerator instead of being fully buffered. See stream below for details and a request-body example.
const streamedBody = $T.stream($T.byteArray()) Request Schema Definition
The Request Schema definition allows you to define a schema for your request in your Route Definition. It must be defined right after the route path.
const schema = {}
galbe.get('/foo/:bar', schema, ctx => {}) The Request Schema has five optional properties: headers, params, query, body, and response.
headers
headers: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral | STUnion } Defines request headers with their respective Schema types.
Example:
const schema = {
headers: {
'user-agent': $T.optional($T.string({ pattern: /^Bun/ }))
}
} Note
Header names are matched case-insensitively.
params
params: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral | STUnion } Defines route parameters with their respective Schema types.
Example:
const schema = {
params: {
name: $T.string(),
age: $T.integer({ min: 0 })
}
} Warning
Every key must match an existing parameter declared in the route path. Otherwise, TypeScript will report an error. If no schema is defined for a given parameter, Galbe treats it as a
string.
query
query: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral | STUnion | STArray } Defines query parameters with their respective Schema types.
Example:
const schema = {
query: {
name: $T.literal('Galbe'),
list: $T.array($T.number())
}
} Note
Array query parameters can be provided either by repeating the key (
?list=1&list=2) or as a comma-separated value (?list=1,2).
body
body: {
byteArray?: STByteArray | STStream
text?: STString | STLiteral | STBoolean | STNumber | STInteger | STUnion | STStream
json?: STJson | STObject | STBoolean | STInteger | STNumber | STString | STArray | STUnion | STIntersection
urlForm?: STObject | STStream | STUnion
multipart?: STMultipartForm | STStream | STUnion
default?: STString | STByteArray | STStream | STAny
} Defines the request body schema based on content type. The matching schema is selected from the request's Content-Type header, then the body is parsed and validated. The default key is used when no other entry matches the content type.
Byte Array
Matches an application/octet-stream request body.
const body = {
byteArray: $T.byteArray()
} Text
Matches a text/* request body.
const body = {
text: $T.string()
} JSON
Matches an application/json request body.
const body = {
json: $T.object({
name: $T.string(),
age: $T.integer({ min: 0 })
})
} URL Form
Matches an application/x-www-form-urlencoded request body.
const body = {
urlForm: $T.object({
name: $T.string(),
age: $T.integer({ min: 0 })
})
} Multipart Form
Matches a multipart/form-data request body.
const body = {
multipart: $T.multipartForm({
name: $T.string(),
age: $T.integer({ min: 0 })
})
} Inside a multipart handler, each part is exposed as { headers: { name, type?, filename? }, content }.
stream
Certain request body types can be streamed using the stream wrapper, improving performance by validating data incrementally.
This is useful for heavy body payloads, since it enables early validation and fail-fast behavior.
Example
Consider a multipart/form-data body with two fields, username and heavyImageFile:
galbe.post(
'/user/create',
{
body: {
multipart: $T.multipartForm({
username: $T.string(),
heavyImageFile: $T.byteArray()
})
}
},
ctx => {
// At this point, the full request body has already been processed.
if (!isValid(ctx.body.username))
throw new RequestError({ status: 400 })
else ctx.set.status = 201
}
) Even if username fails validation, the entire body — including heavyImageFile — is processed before the response is sent. That's wasted time and memory because heavyImageFile is never used.
A better approach is to use the stream wrapper to enable early validation and fail-fast behavior. When the body schema is wrapped in $T.stream(...), ctx.body becomes an AsyncGenerator instead of a fully-parsed object.
galbe.post(
'/user/create',
{
body: {
multipart: $T.stream($T.multipartForm({
username: $T.string(),
heavyImageFile: $T.byteArray()
}))
}
},
async ctx => {
// At this point, the body has not been processed yet.
for await (const { headers, content } of ctx.body) {
if (headers.name === 'username' && !isValid(content)) {
// Returns an early response before heavyImageFile is processed
throw new RequestError({ status: 400 })
}
}
ctx.set.status = 201
}
) response
response: Record<number | 'default', STByteArray | STString | STBoolean | STNumber | STInteger | STLiteral | STObject | STArray | STUnion | STIntersection | STStream | STAny | STNull> Defines response validation by associating schema types with specific HTTP status codes. The special key default matches any status code that doesn't have an explicit entry.
Example:
const response = {
200: $T.object({ data: $T.array($T.number()) }),
404: $T.literal('Not found'),
default: $T.string()
} This ensures every response adheres to the defined schema.
Note
Response validation is enabled by default: any endpoint response with a matching schema is validated at runtime. To disable runtime validation, set
responseValidator.enabledtofalsein the Configuration.