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. Official Type Provider packages follow a @fastify/type-provider-{provider-name} naming convention, and there are several community ones available as well.

The following inference packages are supported:

See also the Type Provider wrapper packages for each of the packages respectively:

Json Schema to Ts

The following sets up a json-schema-to-ts Type Provider

  1. $ npm i @fastify/type-provider-json-schema-to-ts
  1. import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
  2. import fastify from 'fastify'
  3. const server = fastify().withTypeProvider<JsonSchemaToTsProvider>()
  4. server.get('/route', {
  5. schema: {
  6. querystring: {
  7. type: 'object',
  8. properties: {
  9. foo: { type: 'number' },
  10. bar: { type: 'string' },
  11. },
  12. required: ['foo', 'bar']
  13. }
  14. }
  15. }, (request, reply) => {
  16. // type Query = { foo: number, bar: string }
  17. const { foo, bar } = request.query // type safe!
  18. })

TypeBox

The following sets up a TypeBox Type Provider

  1. $ npm i @fastify/type-provider-typebox
  1. import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
  2. import { Type } from '@sinclair/typebox'
  3. import fastify from 'fastify'
  4. const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
  5. server.get('/route', {
  6. schema: {
  7. querystring: Type.Object({
  8. foo: Type.Number(),
  9. bar: Type.String()
  10. })
  11. }
  12. }, (request, reply) => {
  13. // type Query = { foo: number, bar: string }
  14. const { foo, bar } = request.query // type safe!
  15. })

See also the TypeBox documentation on how to set up AJV to work with TypeBox.

Zod

See official documentation for Zod type provider instructions.

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:

  1. import Fastify from 'fastify'
  2. import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
  3. import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
  4. import { Type } from '@sinclair/typebox'
  5. const fastify = Fastify()
  6. function pluginWithTypebox(fastify: FastifyInstance, _opts, done): void {
  7. fastify.withTypeProvider<TypeBoxTypeProvider>()
  8. .get('/', {
  9. schema: {
  10. body: Type.Object({
  11. x: Type.String(),
  12. y: Type.Number(),
  13. z: Type.Boolean()
  14. })
  15. }
  16. }, (req) => {
  17. const { x, y, z } = req.body // type safe
  18. });
  19. done()
  20. }
  21. function pluginWithJsonSchema(fastify: FastifyInstance, _opts, done): void {
  22. fastify.withTypeProvider<JsonSchemaToTsProvider>()
  23. .get('/', {
  24. schema: {
  25. body: {
  26. type: 'object',
  27. properties: {
  28. x: { type: 'string' },
  29. y: { type: 'number' },
  30. z: { type: 'boolean' }
  31. },
  32. }
  33. }
  34. }, (req) => {
  35. const { x, y, z } = req.body // type safe
  36. });
  37. done()
  38. }
  39. fastify.register(pluginWithJsonSchema)
  40. 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 below:

  1. import Fastify from 'fastify'
  2. import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
  3. import { Type } from '@sinclair/typebox'
  4. const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
  5. server.register(plugin1) // wrong
  6. server.register(plugin2) // correct
  7. function plugin1(fastify: FastifyInstance, _opts, done): void {
  8. fastify.get('/', {
  9. schema: {
  10. body: Type.Object({
  11. x: Type.String(),
  12. y: Type.Number(),
  13. z: Type.Boolean()
  14. })
  15. }
  16. }, (req) => {
  17. // it doesn't work! in a new scope needs to call `withTypeProvider` again
  18. const { x, y, z } = req.body
  19. });
  20. done()
  21. }
  22. function plugin2(fastify: FastifyInstance, _opts, done): void {
  23. const server = fastify.withTypeProvider<TypeBoxTypeProvider>()
  24. server.get('/', {
  25. schema: {
  26. body: Type.Object({
  27. x: Type.String(),
  28. y: Type.Number(),
  29. z: Type.Boolean()
  30. })
  31. }
  32. }, (req) => {
  33. // works
  34. const { x, y, z } = req.body
  35. });
  36. done()
  37. }

Type Definition of FastifyInstance + TypeProvider

When working with modules one has to make use of FastifyInstance with Type Provider generics. See the example below:

  1. // index.ts
  2. import Fastify from 'fastify'
  3. import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
  4. import { registerRoutes } from './routes'
  5. const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
  6. registerRoutes(server)
  7. server.listen({ port: 3000 })
  1. // routes.ts
  2. import { Type } from '@sinclair/typebox'
  3. import {
  4. FastifyInstance,
  5. FastifyBaseLogger,
  6. RawReplyDefaultExpression,
  7. RawRequestDefaultExpression,
  8. RawServerDefault
  9. } from 'fastify'
  10. import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
  11. type FastifyTypebox = FastifyInstance<
  12. RawServerDefault,
  13. RawRequestDefaultExpression<RawServerDefault>,
  14. RawReplyDefaultExpression<RawServerDefault>,
  15. FastifyBaseLogger,
  16. TypeBoxTypeProvider
  17. >;
  18. export function registerRoutes(fastify: FastifyTypebox): void {
  19. fastify.get('/', {
  20. schema: {
  21. body: Type.Object({
  22. x: Type.String(),
  23. y: Type.Number(),
  24. z: Type.Boolean()
  25. })
  26. }
  27. }, (req) => {
  28. // works
  29. const { x, y, z } = req.body
  30. });
  31. }