Type-Providers
Type Providers
Type Providers are a TypeScript only feature that enables Fastify to statically infer type information directly from inline JSON Schema. They are an alternative to specifying generic arguments on routes; and can greatly reduce the need to keep associated types for each schema defined in your project.
Providers
Type Providers are offered as additional packages you will need to install into
your project. Each provider uses a different inference library under the hood;
allowing you to select the library most appropriate for your needs. Type
Provider packages follow a @fastify/type-provider-{provider-name}
naming
convention.
The following inference packages are supported:
Json Schema to Ts
The following sets up a json-schema-to-ts
Type Provider
$ npm i @fastify/type-provider-json-schema-to-ts
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
import fastify from 'fastify'
const server = fastify().withTypeProvider<JsonSchemaToTsProvider>()
server.get('/route', {
schema: {
querystring: {
type: 'object',
properties: {
foo: { type: 'number' },
bar: { type: 'string' },
},
required: ['foo', 'bar']
}
} as const // don't forget to use const !
}, (request, reply) => {
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
})
TypeBox
The following sets up a TypeBox Type Provider
$ npm i @fastify/type-provider-typebox
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type } from '@sinclair/typebox'
import fastify from 'fastify'
const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
server.get('/route', {
schema: {
querystring: Type.Object({
foo: Type.Number(),
bar: Type.String()
})
}
}, (request, reply) => {
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
})
See also the TypeBox documentation on how to set up AJV to work with TypeBox.
Scoped Type-Provider
The provider types don't propagate globally. In encapsulated usage, one can
remap the context to use one or more providers (for example, typebox
and
json-schema-to-ts
can be used in the same application).
Example:
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
import { Type } from '@sinclair/typebox'
const fastify = Fastify()
function pluginWithTypebox(fastify: FastifyInstance, _opts, done): void {
fastify.withTypeProvider<TypeBoxTypeProvider>()
.get('/', {
schema: {
body: Type.Object({
x: Type.String(),
y: Type.Number(),
z: Type.Boolean()
})
}
}, (req) => {
const { x, y, z } = req.body // type safe
});
done()
}
function pluginWithJsonSchema(fastify: FastifyInstance, _opts, done): void {
fastify.withTypeProvider<JsonSchemaToTsProvider>()
.get('/', {
schema: {
body: {
type: 'object',
properties: {
x: { type: 'string' },
y: { type: 'number' },
z: { type: 'boolean' }
},
} as const
}
}, (req) => {
const { x, y, z } = req.body // type safe
});
done()
}
fastify.register(pluginWithJsonSchema)
fastify.register(pluginWithTypebox)
It's also important to mention that once the types don't propagate globally, currently is not possible to avoid multiple registrations on routes when dealing with several scopes, see bellow:
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type } from '@sinclair/typebox'
const server = Fastify({
ajv: {
customOptions: {
strict: 'log',
keywords: ['kind', 'modifier'],
},
},
}).withTypeProvider<TypeBoxTypeProvider>()
server.register(plugin1) // wrong
server.register(plugin2) // correct
function plugin1(fastify: FastifyInstance, _opts, done): void {
fastify.get('/', {
schema: {
body: Type.Object({
x: Type.String(),
y: Type.Number(),
z: Type.Boolean()
})
}
}, (req) => {
// it doesn't works! in a new scope needs to call `withTypeProvider` again
const { x, y, z } = req.body
});
done()
}
function plugin2(fastify: FastifyInstance, _opts, done): void {
const server = fastify.withTypeProvider<TypeBoxTypeProvider>()
server.get('/', {
schema: {
body: Type.Object({
x: Type.String(),
y: Type.Number(),
z: Type.Boolean()
})
}
}, (req) => {
// works
const { x, y, z } = req.body
});
done()
}
Type Definition of FastifyInstance + TypeProvider
When working with modules one has to make use of FastifyInstance
with Type
Provider generics. See the example below:
// index.ts
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { registerRoutes } from './routes'
const server = Fastify({
ajv: {
customOptions: {
strict: 'log',
keywords: ['kind', 'modifier'],
},
},
}).withTypeProvider<TypeBoxTypeProvider>()
registerRoutes(server)
server.listen({ port: 3000 })
// routes.ts
import { Type } from '@sinclair/typebox'
import {
FastifyInstance,
FastifyLoggerInstance,
RawReplyDefaultExpression,
RawRequestDefaultExpression,
RawServerDefault
} from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
type FastifyTypebox = FastifyInstance<
RawServerDefault,
RawRequestDefaultExpression<RawServerDefault>,
RawReplyDefaultExpression<RawServerDefault>,
FastifyLoggerInstance,
TypeBoxTypeProvider
>;
export function registerRoutes(fastify: FastifyTypebox): void {
fastify.get('/', {
schema: {
body: Type.Object({
x: Type.String(),
y: Type.Number(),
z: Type.Boolean()
})
}
}, (req) => {
// works
const { x, y, z } = req.body
});
}