Categories
Fastify

Server-Side Development with Fastify — Responses

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.

Reply Object

Then reply object lets us send HTTP responses to the client.

We can set the status code with the code method.

For instance, we can write:

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

fastify.get('/', function(req, reply) {
  reply
    .code(200)
    .header('Content-Type', 'application/json; charset=utf-8')
    .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 send a 200 response with the code method.

We can check the status code with the statusCode method.

For example, we can write:

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

fastify.get('/', function(req, reply) {
  if (reply.statusCode >= 299) {
    reply.statusCode = 500
  }
  reply.send()
})

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

to set the statusCode to 500.

Response Headers

We can set the headers of the response with the reply.headers method.

For instance, we can write:

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

fastify.get('/', function(req, reply) {
  reply.headers({
    'x-foo': 'foo',
    'x-bar': 'bar'
  })
  .send()
})

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

Then we send the x-foo and x-bar headers.

We can get the header with the getHeader method.

For instance, we can write:

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

fastify.get('/', function(req, reply) {
  reply.header('x-foo', 'foo')
  console.log(reply.getHeader('x-foo'))
  reply.send()
})

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

We call getHeader to get the header.

Also, we can get all the headers with the getHeaders method:

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

fastify.get('/', function(req, reply) {
  reply.header('x-foo', 'foo')
  reply.header('x-bar', 'bar')
  reply.raw.setHeader('x-foo', 'foo2')
  console.log(reply.getHeaders())
  reply.send()
})

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

To remove a header, we call removeHeader to do so:

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

fastify.get('/', function(req, reply) {
  reply.header('x-foo', 'foo')
  reply.removeHeader('x-foo')
  console.log(reply.getHeader('x-foo'))
  reply.send()
})

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

We called removeHeader to remove the x-foo header, so when we call getHeader on it, we get undefined .

Redirect

We can redirect to a different route with the redirect method.

For example, we can write:

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

fastify.get('/home', function(req, reply) {
  reply.send('home')
})

fastify.get('/', function(req, reply) {
  reply.redirect('/home')
})

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

Then we redirect to the /home route.

Also, we can add the status code by writing:

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

fastify.get('/home', function(req, reply) {
  reply.send('home')
})

fastify.get('/', function(req, reply) {
  reply.code(303).redirect('/home')
})

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

or:

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

fastify.get('/home', function(req, reply) {
  reply.send('home')
})

fastify.get('/', function(req, reply) {
  reply.redirect(303, '/home')
})

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

Conclusion

We can send responses to the client with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Fluent Schema Request 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.

Fluent Schema

We can set up a schema to validate request content with the fluent-schema module in our Fastify app.

For example, we can write:

const fastify = require('fastify')({})
const S = require('fluent-schema')

const MY_KEYS = {
  KEY1: 'ONE',
  KEY2: 'TWO'
}

const bodyJsonSchema = S.object()
  .prop('someKey', S.string())
  .prop('someOtherKey', S.number())
  .prop('requiredKey', S.array().maxItems(3).items(S.integer()).required())
  .prop('nullableKey', S.mixed([S.TYPES.NUMBER, S.TYPES.NULL]))
  .prop('multipleTypesKey', S.mixed([S.TYPES.BOOLEAN, S.TYPES.NUMBER]))
  .prop('multipleRestrictedTypesKey', S.oneOf([S.string().maxLength(5), S.number().minimum(10)]))
  .prop('enumKey', S.enum(Object.values(MY_KEYS)))
  .prop('notTypeKey', S.not(S.array()))

const queryStringJsonSchema = S.object()
  .prop('name', S.string())
  .prop('excitement', S.integer())

const paramsJsonSchema = S.object()
  .prop('par1', S.string())
  .prop('par2', S.integer())

const headersJsonSchema = S.object()
  .prop('x-foo', S.string().required())

const schema = {
  body: bodyJsonSchema,
  querystring: queryStringJsonSchema,
  params: paramsJsonSchema,
  headers: headersJsonSchema
}

fastify.post('/', { schema }, function (req, reply) {
  reply.send('success')
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We add the bodyJsonSchema to validate the request body.

S.object is called to let us validate objects.

prop is used to validate properties. It takes the property name as the first argument.

S.string indicates that it’s a string.

S.array validates that a property is an array.

maxItems validates the max number of items in an array.

S.oneOf lets us specify one of multiple types for a property.

S.enum validates a property is an enum. It takes a string array with the enum values.

We can also specify validation schemas for query strings, URL parameters, and headers with fluent-schema .

Now when we make a request with content that doesn’t match the schema, we’ll get an error.

Schema Reuse

We can reuse our schemas by adding an ID to them with the id method.

For instance, we can write:

const fastify = require('fastify')({})
const S = require('fluent-schema')

const addressSchema = S.object()
  .id('#address')
  .prop('line1').required()
  .prop('line2')
  .prop('country').required()
  .prop('city').required()
  .prop('zip').required()

const commonSchemas = S.object()
  .id('app')
  .definition('addressSchema', addressSchema)

fastify.addSchema(commonSchemas)

const bodyJsonSchema = S.object()
  .prop('home', S.ref('app#address')).required()
  .prop('office', S.ref('app#/definitions/addressSchema')).required()

const schema = { body: bodyJsonSchema }

fastify.post('/', { schema }, function (req, reply) {
  reply.send('success')
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to create our schemas with the addressSchema .

Then we add the definition to a reusable schema with the definition method.

id specifies the ID of the schema.

S.ref lets us reference a schema in the code by its ID so we can reuse them.

Then we create the schema object and pass it to the 2nd argument of post .

We can also create the schema with an object:

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

const sharedAddressSchema = {
  $id: 'sharedAddress',
  type: 'object',
  required: ['line1', 'country', 'city', 'zip'],
  properties: {
    line1: { type: 'string' },
    line2: { type: 'string' },
    country: { type: 'string' },
    city: { type: 'string' },
    zip: { type: 'string' }
  }
}

fastify.addSchema(sharedAddressSchema)

const bodyJsonSchema = {
  type: 'object',
  properties: {
    vacation: 'sharedAddress#'
  }
}

const schema = { body: bodyJsonSchema }

fastify.post('/', { schema }, function(req, reply) {
  reply.send('success')
})

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

We create an object for the schema.

Then we all fastify.addSchema to add the schema.

And then we create the schema object with the added schema which references the schema by the $id property.

And then we add the schema to the route handler.

Conclusion

We can add validation for requests with the fluent-schema library to our Fastify app.

Categories
Fastify

Server-Side Development with Fastify — Request 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.

Request Validation

We can specify how to validate requests with schema objects.

For example, we can write:

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

const bodyJsonSchema = {
  type: 'object',
  required: ['requiredKey'],
  properties: {
    someKey: { type: 'string' },
    someOtherKey: { type: 'number' },
    requiredKey: {
      type: 'array',
      maxItems: 3,
      items: { type: 'integer' }
    },
    nullableKey: { type: ['number', 'null'] },
    multipleTypesKey: { type: ['boolean', 'number'] },
    multipleRestrictedTypesKey: {
      oneOf: [
        { type: 'string', maxLength: 5 },
        { type: 'number', minimum: 10 }
      ]
    },
    enumKey: {
      type: 'string',
      enum: ['John', 'Foo']
    },
    notTypeKey: {
      not: { type: 'array' }
    }
  }
}

const queryStringJsonSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    excitement: { type: 'integer' }
  }
}

const paramsJsonSchema = {
  type: 'object',
  properties: {
    par1: { type: 'string' },
    par2: { type: 'number' }
  }
}

const headersJsonSchema = {
  type: 'object',
  properties: {
    'x-foo': { type: 'string' }
  },
  required: ['x-foo']
}

const schema = {
  body: bodyJsonSchema,
  querystring: queryStringJsonSchema,
  params: paramsJsonSchema,
  headers: headersJsonSchema
}

fastify.post('/', { schema }, (request, reply)=>{
  reply.send('success')
})

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 schemas for the request body, header, query strings, and URL parameters.

bodyJsonSchema has the schema for the body.

The type property specifies its type.

required specifies which fields are required.

properties specifies the properties that the request body can have.

The type in the properties specifies the type.

queryStringJsonSchema specifies what can be included in the query string.

We specify what can be included in the properties property.

paramsJsonSchema is the object for validating URL parameters.

And headersJsonSchema specifies the schema for the request headers.

We pass all of them into the schema object.

And then we pass that into the 2nd argument of fastify.post to let us validate with them.

Ajv Plugins

We can use the Ajv plugin to validate requests.

For instance, we can write:

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

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()

to add validation with the ajv-merge-patch plugin.

We add the plugin with:

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

Then we add the schema object to add the schema.

The source specifies the request body structure with the data types for the properties.

And the with property lets us add more options for validation.

The op is the operation to do with the existing schema.

'add' lets us add more requirements.

path is the property path of the body to validate.

value.type is the data type we want the properties to be.

Conclusion

We can add request validation with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Response Serialization and Request Validation Error Handling

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.

Serialization

We can change the way that responses are serialized.

To do this, we write:

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

const schema = {
  response: {
    '2xx': {
      type: 'object',
      properties: {
        value: { type: 'string' },
        otherValue: { type: 'boolean' }
      }
    },
    201: {
      value: { type: 'string' }
    }
  }
}

fastify.get('/', { schema }, (request, reply) => {
  reply.send({ value: 1, otherValue: 'foo' })
})

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

We add the response schema with the schema.response property.

We specify the response data type and structure for each status code.

The type specifies the data type of the response.

properties specifies the data type for the properties in the response.

In the GET / request handler, we call reply.send with an object with the properties listed in the schema.

And the properties will automatically be converted to the types specified in the schema.

So we get:

{"value":"1","otherValue":true}

when we make a GET request to / .

We can also specify the response schema in the request.

To do this, we write:

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

fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {
  return data => JSON.stringify(data)
})

fastify.get('/', {
  handler (req, reply) {
    reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' })
  },
  schema: {
    response: {
      '2xx': {
        id: { type: 'number' },
        name: { type: 'string' }
      }
    }
  }
})

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.setSerializerCompiler to compile the response.

And we add the schema to the fastify.get method to specify the response schema.

Error Handling

We can change how errors are handled when validation fails.

For example, we can write:

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

const schema = {
  body: {
    type: 'object',
    properties: {
      name: { type: 'string' }
    },
    required: ['name']
  }
}

fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
  if (req.validationError) {
    reply.code(400).send(req.validationError)
  }
  else {
    reply.send('success')
  }
})

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

We specify the validation schema.

And in the fastify.post method, we pass in an object with the schema and attachValidation set to true .

We check if there are any validation errors with the req.validationError property.

And we get the validation errors with the req.validationError property.

Conclusion

We can change how responses are serialized and how errors are handled with Fastify.

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.