Categories
Fastify

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

preParsing Hook

The preParsing hook lets us transform the request payload stream before it’s parsed.

If it returns a value, it must return a stream.

For example, we can write:

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

fastify.addHook('preParsing', (request, reply, payload, done) => {
  done(null, payload)
})

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

to pass payload into the done function.

Also, we can write:

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

fastify.addHook('preParsing', async (request, reply, payload) => {
  return payload
})
const start = async () => {
  try {
    fastify.post('/', function (request, reply) {
      reply.send()
    })
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to return a promise resolving to payload .

preValidation

The preValidation hook lets us run code before validating the request.

It runs after the preParsing hook.

For instance, we can write:

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

fastify.addHook('preValidation', (request, reply, done) => {
  done()
})

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

We can add some checks into the callback.

We can also write:

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

fastify.addHook('preValidation', async (request, reply) => {
  return
})

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

to do the same thing.

preSerialization

The preSerialization hook letsus change or replace the payload before it’s serialized.

For instance, we can write:

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

fastify.addHook('preSerialization', (request, reply, payload, done) => {
  const err = null
  const newPayload = { wrapped: payload }
  done(err, newPayload)
})

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

Then when we make a POST request to / with the payload:

{
    "abc": 123
}

We get:

{
    "wrapped": {
        "abc": 123
    }
}

as the response.

Equivalently, we can write:

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

fastify.addHook('preSerialization', async (request, reply, payload) => {
  return { wrapped: payload }
})

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

and get the same result.

This hook isn’t called if the payload is a string, buffer, stream or null .

onError

The onError hook lets us catch errors that occur in our app.

For example, we can write:

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

fastify.addHook('onError', (request, reply, error, done) => {
  done()
})

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

to catch errors. error has the error data.

Likewise, we can write:

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

fastify.addHook('onError', async (request, reply, error) => {
  console.log(error)
})

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

to do the same thing.

Conclusion

We can add hooks to listen to various events with Fastify.

Categories
Fastify

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

Middleware

Middleware support doesn’t come out of the box with the Fastify framework since version 3.

If we want to add support for Express style middleware, we need to install fastify-express or middle plugin.

For example, we can write:

const fastify = require('fastify')()

const start = async () => {
  try {
    await fastify.register(require('fastify-express'))
    fastify.use(require('cors')())

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

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

We add:

await fastify.register(require('fastify-express'))
fastify.use(require('cors')())

to add the fastify-express plugin with the cors middleware.

To use the middle module, we write:

const fastify = require('fastify')()

const start = async () => {
  try {
    await fastify.register(require('middie'))
    fastify.use(require('cors')())

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

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

to add the middleware and add the cors middleware.

Restrict Middleware Execution to a Certain Paths

We can restrict middleware execution to certain paths.

For example, we can write:

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

const start = async () => {
  try {
    await fastify.register(require('fastify-express'))
    const serveStatic = require('serve-static')

    fastify.use('/css', serveStatic(path.join(__dirname, '/assets')))
    fastify.use('/css/(.*)', serveStatic(path.join(__dirname, '/assets')))
    fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))

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

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

We call fastify.use with the path that the middleware runs in as the first argument.

The 2nd argument is the middleware that we want to run.

This doesn’t work with route with parameters.

Fastify offers some alternatives to the most commonly used middlewares, such as fastify-helmet in the case of helmet, fastify-cors for cors and fastify-static for serve-static.

Hooks

We can register hooks with the fastify.addHook method.

For example, we can write:

const fastify = require('fastify')({})
const asyncMethod = () => Promise.resolve('foo')

const start = async () => {
  try {
    fastify.addHook('onRequest', async (request, reply) => {
      await asyncMethod()
      return
    })

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

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

to call addHook for the onRequest event.

Also, we can write:

const fastify = require('fastify')({})
fastify.addHook('onRequest', (request, reply, done) => {
  done()
})
const start = async () => {
  try {
    fastify.get('/', function (request, reply) {
      reply.send({ hello: 'world' })
    })

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

to do the same thing. We call done to indicate the hook has finished running.

Conclusion

We can add Express middleware and hooks with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Customize Logging

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.

Customize Logging

We can also customize how responses are logged.

For instance, we can write:

const fastify = require('fastify')({
  logger: {
    prettyPrint: true,
    serializers: {
      res (reply) {
        return {
          statusCode: reply.statusCode
        }
      },
      req (request) {
        return {
          method: request.method,
          url: request.url,
          path: request.path,
          parameters: request.parameters,
          headers: request.headers
        };
      }
    }
  }
});

fastify.get('/', (request, reply) => {
  request.log.info('Some info')
  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 serialize the response log content with the res method.

And we do the same for the request with the req method.

We set prettyPrint to true to pretty print our output.

We can add a hook to our request to log the request payload by writing:

const fastify = require('fastify')({
  logger: 'info'
});

fastify.addHook('preHandler', (req, reply, done) => {
  if (req.body) {
    req.log.info({ body: req.body }, 'parsed body')
  }
  done()
})

fastify.post('/', (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()

We get the req.body and put that in the req.log.info call.

Also, we can use the logger outside route handlers.

For instance, we can write:

const log = require('pino')({ level: 'info' })
const fastify = require('fastify')({ logger: log })

log.info('does not have request information')

fastify.get('/', function (request, reply) {
  request.log.info('includes request information, but is the same logger instance as `log`')
  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()

We call log.info outside the route handler.

And we call request.log.info inside it.

We can redact log info with the redact property.

For example, we can write:

const split = require('split2')
const stream = split(JSON.parse)

const fastify = require('fastify')({
  logger: {
    stream,
    redact: ['req.headers.authorization'],
    level: 'info',
    serializers: {
      req (request) {
        return {
          method: request.method,
          url: request.url,
          headers: request.headers,
          hostname: request.hostname,
          remoteAddress: request.ip,
          remotePort: request.socket.remotePort
        }
      }
    }
  }
})

fastify.get('/', function (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()

We add the redact property to add the property paths to skip in ur log.

Now we won’t see the value of req.headers.authorization in our log.

Conclusion

We can customize our logging with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Versioning and Logging

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.

Version

We can set the version to accept with the version property.

For instance, we can write:

const fastify = require('fastify')()

fastify.route({
  method: 'GET',
  url: '/',
  version: '1.2.0',
  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()

Then our / route will check the Accept-Version header from the request to see if it matches what we have.

If we make a request with Accept-Version set to 1.x , 1.2.0 or 1.2.x , then we see:

{ hello: 'world' }

in the response.

Otherwise, we get 404.

Logging

Logging is disabled by default.

We can enable logging by setting the logger property.

For instance, we can write:

const fastify = require('fastify')({ logger: true })

fastify.get('/', (request, reply) => {
  request.log.info('Some info')
  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 enable logging with logger set to true .

Then we call request.log.info to log something to the console.

We should see something like.

{"level":30,"time":1604268389762,"pid":737,"hostname":"9cc07d2eed9d","reqId":2,"msg":"Some info"}

logged.

We can also pass some options to the logger.

For example, we can write:

const fastify = require('fastify')({
  logger: {
    level: 'info',
    file: './log.txt'
  }
})

fastify.get('/', (request, reply) => {
  request.log.info('Some info')
  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()

Then we log the output to log.txt .

We can pass a custom stream to the Pino instance.

To do this, we write:

const split = require('split2')
const stream = split(JSON.parse)

const fastify = require('fastify')({
  logger: {
    level: 'info',
    stream: stream
  }
})
fastify.get('/', (request, reply) => {
  request.log.info('Some info')
  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 pass in a custom stream that’s created with:

const stream = split(JSON.parse)

Also, we can customize what’s logged with the serializers property.

We can write:

const fastify = require('fastify')({
  logger: {
    serializers: {
      req (request) {
        return { url: request.url }
      }
    }
  }
})

fastify.get('/', (request, reply) => {
  request.log.info('Some info')
  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 serializers property which has the req function to return an object with the data we want to record from the request.

Conclusion

We can set the version of the API and adding logging with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Custom Logging and Route Config

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.

Custom Log Level

We can change the log level of our Fastify app.

For instance, we can write:

index.js

const fastify = require('fastify')({
  logger: true
})

fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
fastify.register(require('./routes/v2/users'), { prefix: '/v2' })

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

routes/v1/users.js

module.exports = function (fastify, opts, done) {
  fastify.get('/user', async () => 'v1')
  done()
}

routes/v2/user.js

module.exports = function (fastify, opts, done) {
  fastify.get('/user', async () => 'v2')
  done()
}

We set logger to true to enable logging.

We can also set the logging levels for individual routes.

For instance, we can write:

const fastify = require('fastify')()

fastify.get('/', { logLevel: 'warn' }, (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()

We set the logLevel to 'warn' on the / route.

Custom Log Serializer

We can customize the log serializer.

For instance, we can write:

const fastify = require('fastify')({
  logger: {
    level: 'info',
    serializers: {
      user (req) {
        return {
          method: req.method,
          url: req.url,
          headers: req.headers,
          hostname: req.hostname,
          remoteAddress: req.ip,
          remotePort: req.socket.remotePort
        }
      }
    }
  }
})

fastify.register(context1, {
  logSerializers: {
    user: value => `My serializer father - ${value}`
  }
})

async function context1 (fastify, opts) {
  fastify.get('/', (req, reply) => {
    req.log.info({ user: 'call father serializer', key: 'another key' })
    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 add the logSerializers property.

We configure the logger when we call the Fastify factory function on the first line.

We set the level to set the log level.

serializers has an object with the user function that returns the info from the request.

We return the request method, URL, headers, hostname, IP, and remote port respectively.

Then when we make the request, we see the output in the console.

Route Config

We can pass in a config object when we define our route.

For instance, we can write:

const fastify = require('fastify')()

function handler (req, reply) {
  reply.send(reply.context.config.output)
}

fastify.get('/en', { config: { output: 'hello world!' } }, handler)
fastify.get('/fr', { config: { output: 'bonjour' } }, handler)

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

We pass in an object with the config property in the 2nd argument.

Then we can get that value from the reply.context property.

Conclusion

We can set the custom logging and set route config with Fastify.