Skip to content

Instantly share code, notes, and snippets.

@vesse
Last active November 9, 2021 14:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vesse/cf6aaaee840db367babea1b244a3b77d to your computer and use it in GitHub Desktop.
Save vesse/cf6aaaee840db367babea1b244a3b77d to your computer and use it in GitHub Desktop.
Fastify + TypeBox fully typed
import { Static, Type } from '@sinclair/typebox'
import fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify'
import type { RouteGenericInterface, RouteHandler } from 'fastify/types/route'
const port = 8080
const requestBodySchema = Type.Object({
email: Type.Readonly(Type.String({ format: 'email' })),
})
const responseBodySchema = Type.Object({
ok: Type.Boolean(),
})
const queryStringSchema = Type.Object({
test: Type.ReadonlyOptional(Type.Number({ minimum: 200 })),
})
type RequestBody = Static<typeof requestBodySchema>
type ResponseBody = Static<typeof responseBodySchema>
type QueryString = Static<typeof queryStringSchema>
interface DummyRoute extends RouteGenericInterface {
Body: RequestBody
Querystring: QueryString
Reply: ResponseBody
}
const handler: RouteHandler<DummyRoute> = async (req, res) => {
console.log('Caller email', req.body.email)
console.log('Test', req.query.test)
return res.send({ ok: true })
}
const routes: FastifyPluginAsync = (instance) => {
instance.route<DummyRoute>({
method: 'POST',
url: '/dummy',
handler: handler,
schema: {
body: requestBodySchema,
querystring: queryStringSchema,
response: {
200: responseBodySchema,
},
},
})
return Promise.resolve()
}
const initApp = async (): Promise<FastifyInstance> => {
const app = fastify()
await app.register(routes, { prefix: '/api' })
return app
}
initApp()
.then((app) => {
app.listen(port, () => {
console.log('App listening on port', port)
})
})
.catch((err) => {
console.error('Failed to start', err)
})

Fastify + TypeBox

Just a reminder for self -type of gist, and overly verbose on purpose.

Example returns

Body missing

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body should be object"
}

Body not valid

With payload { "lol": "kissa" }, returns

{ 
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body should have required property 'email'"
 }

and with payload { "email": "kaunis kissa" } returns

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body.email should match format \"email\""
}

Query string number too small

Eg. ?test=2 in query string returns

{ 
  "statusCode": 400,
  "error": "Bad Request",
  "message": "querystring.test should be >= 200"
}

Invalid response

Typings prevent returning invalid responses, but had we managed to return { "nok": true } the response to the caller would be

{
  "statusCode": 500,
  "error": "Internal Server Error",
  "message": "\"ok\" is required!"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment