Categories
Fastify

Server-Side Development with Fastify — Request Validation with External Libraries

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Ajv Validation Options

We can add validation options with the fastify.setValidatorCompiler method.

For example, we can write:

const fastify = require('fastify')({
  ajv: {
    plugins: [
      require('ajv-merge-patch')
    ]
  }
})

const Ajv = require('ajv')
const ajv = new Ajv({
  removeAdditional: true,
  useDefaults: true,
  coerceTypes: true,
  nullable: true,
})
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
  return ajv.compile(schema)
})

fastify.post('/', {
  handler (req, reply) { reply.send({ ok: 1 }) },
  schema: {
    body: {
      $patch: {
        source: {
          type: 'object',
          properties: {
            q: {
              type: 'string'
            }
          }
        },
        with: [
          {
            op: 'add',
            path: '/properties/q',
            value: { type: 'number' }
          }
        ]
      }
    }
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call the Ajv constructor and call the serValidationCompiler method with it to add the options from the object we pass into the Ajv constructor.

We can validate options with other libraries.

For example, we can use the @hapi/joi component.

To use it, we write:

const fastify = require('fastify')({})
const Joi = require('@hapi/joi')

fastify.post('/', {
  schema: {
    body: Joi.object().keys({
      hello: Joi.string().required()
    }).required()
  },
  validatorCompiler: ({ schema, method, url, httpPart }) => {
    return data => schema.validate(data)
  }
}, (request, reply) => {
  reply.send('hello')
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add the module.

We call the Joi.object method to make sure the request body is an object.

And we call required to make it required.

Also, we add the validatorCompiler method to add a function to connect the schema to the route.

Also, we can use the yup library to add validator for requests.

For instance, we can write:

const fastify = require('fastify')({})
const yup = require('yup')
const yupOptions = {
  strict: false,
  abortEarly: false,
  stripUnknown: true,
  recursive: true
}

fastify.post('/', {
  schema: {
    body: yup.object({
      age: yup.number().integer().required(),
      sub: yup.object().shape({
        name: yup.string().required()
      }).required()
    })
  },
  validatorCompiler: ({ schema, method, url, httpPart }) => {
    return (data) => {
      try {
        const result = schema.validateSync(data, yupOptions)
        return { value: result }
      } catch (e) {
        return { error: e }
      }
    }
  }
}, (request, reply) => {
  reply.send('hello')
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add validation for the request body.

We call yup.number().integer().required() to make a required number field.

And we call yup.object().shape() to validate objects.

And we call yup.string().required() to add a required string field.

Conclusion

We can add validations for requests with external libraries with Fastify apps.

Categories
Fastify

Server-Side Development with Fastify — Decorator Getters and Setters and Validation

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Decorator Getters and Setters

We can add getters and setters to decorators.

For example, we can write:

const fastify = require('fastify')({})

fastify.decorate('foo', {
  getter () {
    return 'a getter'
  }
})

fastify.get('/', async function (request, reply) {
  reply.send({hello: fastify.foo})
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then fastify.foo returns 'a getter' .

So we get:

{"hello":"a getter"}

as the response of the / route.

Validation

We can add validation for requests.

For example, we can write:

const fastify = require('fastify')({})

fastify.addSchema({
  $id: 'http://example.com/',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

fastify.post('/', {
  handler (request, reply) {
    reply.send('hello')
  },
  schema: {
    body: {
      type: 'array',
      items: { $ref: 'http://example.com#/properties/hello' }
    }
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.addSchema to add the schema.

We specify that it is a string with the properties property.

Then we reference it in the object in the body.

body specifies the body structure.

We have the items property with the $ref property that references the properties.hello property to get the data type of the item of the array.

So when we make a POST request to the / route with a body like:

[
   "abc"
]

we see 'hello' in the response.

Otherwise, we get a 400 error.

$ref can also be used as a root reference.

For example, we can write:

const fastify = require('fastify')({})

fastify.addSchema({
  $id: 'commonSchema',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

fastify.post('/', {
  handler (request, reply) {
    reply.send('hello')
  },
  schema: {
    body: { $ref: 'commonSchema#' },
    headers: { $ref: 'commonSchema#' }
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then when we send an object with the request body, we, get the 'hello' response.

Retrieving Shared Schemas

We can get a shared schema.

For example, we can write:

const fastify = require('fastify')({})

fastify.addSchema({
  $id: 'schemaId',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

const mySchemas = fastify.getSchemas()
const mySchema = fastify.getSchema('schemaId')
console.log(mySchemas, mySchema)

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call addSchema to add our schema.

Then we can get the schemas with the getSchemas method.

The getSchema method gets the schema with the given ID.

We can also use that within the select scope.

For instance, we can write:

const fastify = require('fastify')({})

fastify.addSchema({ $id: 'one', my: 'hello' })

fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })

fastify.register((instance, opts, done) => {
  instance.addSchema({ $id: 'two', my: 'ciao' })
  instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })

  instance.register((subinstance, opts, done) => {
    subinstance.addSchema({ $id: 'three', my: 'hola' })
    subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })
    done()
  })
  done()
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then we call addSchema to add the schema inside the given context.

It’ll only be available within the given context.

getSchemas also get the schemas within the given context.

So when we make a request to the / route, we get:

{"one":{"$id":"one","my":"hello"}}

as the response.

Conclusion

We can add decorators with getters and setters, and we can add request validation schemas with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Decorator Getters and Setters and Validation

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Decorator Getters and Setters

We can add getters and setters to decorators.

For example, we can write:

const fastify = require('fastify')({})

fastify.decorate('foo', {
  getter () {
    return 'a getter'
  }
})

fastify.get('/', async function (request, reply) {
  reply.send({hello: fastify.foo})
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then fastify.foo returns 'a getter' .

So we get:

{"hello":"a getter"}

as the response of the / route.

Validation

We can add validation for requests.

For example, we can write:

const fastify = require('fastify')({})

fastify.addSchema({
  $id: 'http://example.com/',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

fastify.post('/', {
  handler (request, reply) {
    reply.send('hello')
  },
  schema: {
    body: {
      type: 'array',
      items: { $ref: 'http://example.com#/properties/hello' }
    }
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.addSchema to add the schema.

We specify that it is a string with the properties property.

Then we reference it in the object in the body.

body specifies the body structure.

We have the items property with the $ref property that references the properties.hello property to get the data type of the item of the array.

So when we make a POST request to the / route with a body like:

[
   "abc"
]

we see 'hello' in the response.

Otherwise, we get a 400 error.

$ref can also be used as a root reference.

For example, we can write:

const fastify = require('fastify')({})

fastify.addSchema({
  $id: 'commonSchema',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

fastify.post('/', {
  handler (request, reply) {
    reply.send('hello')
  },
  schema: {
    body: { $ref: 'commonSchema#' },
    headers: { $ref: 'commonSchema#' }
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then when we send an object with the request body, we, get the 'hello' response.

Retrieving Shared Schemas

We can get a shared schema.

For example, we can write:

const fastify = require('fastify')({})

fastify.addSchema({
  $id: 'schemaId',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

const mySchemas = fastify.getSchemas()
const mySchema = fastify.getSchema('schemaId')
console.log(mySchemas, mySchema)

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call addSchema to add our schema.

Then we can get the schemas with the getSchemas method.

The getSchema method gets the schema with the given ID.

We can also use that within the select scope.

For instance, we can write:

const fastify = require('fastify')({})

fastify.addSchema({ $id: 'one', my: 'hello' })

fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })

fastify.register((instance, opts, done) => {
  instance.addSchema({ $id: 'two', my: 'ciao' })
  instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })

  instance.register((subinstance, opts, done) => {
    subinstance.addSchema({ $id: 'three', my: 'hola' })
    subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })
    done()
  })
  done()
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then we call addSchema to add the schema inside the given context.

It’ll only be available within the given context.

getSchemas also get the schemas within the given context.

So when we make a request to the / route, we get:

{"one":{"$id":"one","my":"hello"}}

as the response.

Conclusion

We can add decorators with getters and setters, and we can add request validation schemas with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Route Level Hooks and Decorators

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Route Level Hooks

We can add hooks at the route level.

To do this, we can write:

const fastify = require('fastify')({})

fastify.route({
  method: 'GET',
  url: '/',
  schema: { },
  onRequest (request, reply, done) {
    done()
  },
  onResponse (request, reply, done) {
    done()
  },
  preParsing (request, reply, done) {
    done()
  },
  preValidation (request, reply, done) {
    done()
  },
  preHandler (request, reply, done) {
    done()
  },
  preSerialization (request, reply, payload, done) {
    done(null, payload)
  },
  handler (request, reply) {
    reply.send({ hello: 'world' })
  }
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add the hooks.

onRequest is run when a request is received.

onResponse is run when a response is sent.

preParsing is run when the request is parsed.

preValidation is run before validation is done on the request.

preHandler is run after the shared preHandler hooks.

preSerialization runs after the shared preSerialization hook, which is run after the response is serialized.

handler is the route handler method.

Decorators

We can use decorators to customize core Fastify objects, such as the request and reply objects.

To add one and use them we can write:

const fastify = require('fastify')({})

fastify.decorateRequest('user', '')

fastify.addHook('preHandler', (req, reply, done) => {
  req.user = 'jane smith'
  done()
})

fastify.get('/', (req, reply) => {
  reply.send(`Hello, ${req.user}!`)
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.decoratorRequest method to add a user property the req object.

Then we set the req.user property in the preHandler hook to 'jane smith' .

Then in our route handler, we can access the value.

Therefore, we get:

Hello, jane smith!

returned as the response.

We can use the fastify.decorate method to decorate the Fastify server instance.

For instance, we can write:

const fastify = require('fastify')({})

fastify.decorate('conf', {
  db: 'some.db',
  port: 3000
})

fastify.get('/', (req, reply) => {
  reply.send(`Hello ${fastify.conf.db}`)
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add data to the fastify object.

We add the conf property to it.

Then we can access the value anywhere in our app.

So we should get:

Hello some.db

from the response.

The decorated Fastify server can also be accessed with the this keyword from the route handler.

For instance, we can write:

const fastify = require('fastify')({})

fastify.decorate('conf', {
  db: 'some.db',
  port: 3000
})

fastify.get('/', async function (request, reply) {
  reply.send({hello: this.conf.db})
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then we get:

{"hello":"some.db"}

from the response.

Conclusion

We can add data to the Fastify instance or requests with Fastify decorators.

Also, we can add hooks at the route level.

Categories
Fastify

Server-Side Development with Fastify — App Hooks

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Respond to a Request from a Hook

We can send responses from hooks.

For example, we can write:

const fastify = require('fastify')({})

fastify.addHook('onRequest', (request, reply, done) => {
  reply.send('Early response')
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call reply.send in the onRequest hook, so we see:

Early response

in the response.

Also, we can write:

const fastify = require('fastify')({})

fastify.addHook('preHandler', async (request, reply) => {
  reply.send('Early response')
  return reply
})

const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to get the same result with an async function.

Application Hooks

Fastify gs 4 application hooks. They are:

  • onReady — runs the server starts listening to requests
  • onClose — runs when fastify.close() is run to stop the server
  • onRoute — runs when a route is registered
  • onRegister — runs when a new plugin is registered and new encapsulation context is created.

For example we can write:

const fastify = require('fastify')({})

fastify.addHook('onRoute', (routeOptions) => {
  console.log(routeOptions.method)
  console.log(routeOptions.schema)
  console.log(routeOptions.url)
  console.log(routeOptions.path)
  console.log(routeOptions.routePath)
  console.log(routeOptions.bodyLimit)
  console.log(routeOptions.logLevel)
  console.log(routeOptions.logSerializers)
  console.log(routeOptions.prefix)
})

const start = async () => {
  try {
    fastify.get('/', function(request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to use the onRoute hook.

routeOptions has properties about the route.

method has the request method. url has the request URL.

path is the alias for url .

routePath is the URL of the route without the prefix.

bodyLimit has the max request size.

To use the onRegister hook, we write:

const fastify = require('fastify')({})

fastify.decorate('data', [])

fastify.register(async (instance, opts) => {
  instance.data.push('hello')
  console.log(instance.data)

instance.register(async (instance, opts) => {
    instance.data.push('world')
    console.log(instance.data)
  }, { prefix: '/hola' })
}, { prefix: '/ciao' })

fastify.register(async (instance, opts) => {
  console.log(instance.data)
  console.log(opts.prefix)
}, { prefix: '/hello' })

fastify.addHook('onRegister', (instance, opts) => {
  instance.data = instance.data.slice()
  console.log(opts.prefix)
})

const start = async () => {
  try {
    fastify.get('/', function(request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call instance.data.push to add data we use in the plugin.

opts.prefix has the prefix property from the 2nd argument.

Hook Scope

Hooks have their own scope.

We can access it with the this object.

For instance, we can write:

const fastify = require('fastify')({})

fastify.decorate('foo', 'bar')

fastify.addHook('onRequest', function (request, reply, done) {
  const self = this
  console.log(self.foo)
  done()
})

const start = async () => {
  try {
    fastify.get('/', function(request, reply) {
      reply.send('some-text')
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We call fastify.decorate to add properties to this in the onRequest hook.

The first argument is the property name and the 2nd is the value.

So self.foo should be 'bar' .

Conclusion

We can add hooks to our Fastify app to run code at various parts of the app’s lifecycle.