Categories
Fastify

Server-Side Development with Fastify — Changing and Sending 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.

Not Found Response

We can call callNotFound to invoke the custom not found handler.

For example, we can write:

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

fastify.setNotFoundHandler({
  preValidation: (req, reply, done) => {
    done()
  },
  preHandler: (req, reply, done) => {
    done()
  }
}, function (request, reply) {
  reply.send('not found')
})

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

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

We call setNotFoundHandler to add the not found handler.

Then we call reply.callNotFound in our event handler to redirect to the not found handler.

So we see not found as the response of the / route.

Response Time

We can get the response time with the getResponseTime method.

For example, we can write:

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

fastify.get('/', function(req, reply) {
  const milliseconds = reply.getResponseTime()
  reply.send(milliseconds)
})

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

We call it in our handler to get the response time.

Response Content-Type

We can set the response Content-Type header with the reply.type method.

For example, we can write:

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

fastify.get('/', function(req, reply) {
  reply.type('text/html').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 set the Content-Type response header with the reply.type method.

Raw Response

We can send a raw response with the reply.raw property.

For instance, we can write:

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

fastify.get('/', function(req, reply) {
  reply.setCookie('session', 'value', { secure: false })
  reply.raw.writeHead(200, { 'Content-Type': 'text/plain' })
  reply.raw.write('ok')
  reply.raw.end()
})

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

We call setCookie to set our response cookie.

reply.raw.writeHeader adds the response header.

reply.raw.write writes the response body.

reply.raw.end ends the response.

Set if a Response is Sent

We can set the sent property of the response.

For example, we can write:

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

fastify.get('/', function(req, reply) {
  reply.sent = true
  reply.raw.end('hello world')
  return Promise.resolve('this will be skipped')
})

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 reply.sent to true to mark the response as sent.

Send a Response

We can send the response with the reply.send method.

For instance, we can write:

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

fastify.get('/', function(req, 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 send the response.

Conclusion

We can set various parts of a response and send it with Fastify.

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.