Categories
Fastify

Server-Side Development with Fastify — Initial Config and Routes

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.

Initial Config

We can set the initial config of the app with we initialize Fastify.

To do this, we write:

const { readFileSync } = require('fs')
const Fastify = require('fastify')

const fastify = Fastify({
  https: {
    allowHTTP1: true,
    key: readFileSync('./fastify.key'),
    cert: readFileSync('./fastify.cert')
  },
  logger: { level: 'trace'},
  ignoreTrailingSlash: true,
  maxParamLength: 200,
  caseSensitive: true,
  trustProxy: '127.0.0.1,192.168.1.1/24',
})

console.log(fastify.initialConfig)

fastify.get('/', async (request, reply) => {
  return '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 set the initial config.

https has HTTPS options.

allowHTTP1 lets us listen for HTTP1 requests.

key and cert are the SSL key and certificate files.

logger sets the logging level.

ignoreTrailingSlash lets Fastify ignore trailing slashes when matching routes.

caseSensitive lets us set whether the enable case sensitive route matching.

trustProxy lets us set which proxy to trust.

We get the config with the fastify.initialConfig property.

Also, we can set the options on the fly with:

const { readFileSync } = require('fs')
const Fastify = require('fastify')

const fastify = Fastify({
  https: {
    allowHTTP1: true,
    key: readFileSync('./fastify.key'),
    cert: readFileSync('./fastify.cert')
  },
  logger: { level: 'trace'},
  ignoreTrailingSlash: true,
  maxParamLength: 200,
  caseSensitive: true,
  trustProxy: '127.0.0.1,192.168.1.1/24',
})

console.log(fastify.initialConfig)

fastify.register(async (instance, opts) => {
  instance.get('/', async (request, reply) => {
    return instance.initialConfig
  })

  instance.get('/error', async (request, reply) => {
    instance.initialConfig.https.allowHTTP1 = false
    return instance.initialConfig
  })
})

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

We write instance.initialConfig.https.allowHTTP1 = false to set the allowHTTP1 option in the /error route handler.

Properties that can be exposed include:

  • connectionTimeout
  • keepAliveTimeout
  • bodyLimit
  • caseSensitive
  • http2
  • https (it will return false/true or { allowHTTP1: true/false } if explicitly passed)
  • ignoreTrailingSlash
  • maxParamLength
  • onProtoPoisoning
  • pluginTimeout
  • requestIdHeader

Routes

We can declare routes with the fastify.route method.

For instance, we can write:

const fastify = require('fastify')()

fastify.route({
  method: 'GET',
  url: '/',
  schema: {
    querystring: {
      name: { type: 'string' },
      excitement: { type: 'integer' }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  },
  handler: 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()

to add a / route.

We specify the query string that it accepts with the querystring property.

And we specify the response that it returns with the response property.

The handler has the route handler function.

reply.send sends the response to the user.

Conclusion

We can set the initial config of our Fastify app and add routes to our Fastify app with fastify.route .

Categories
Fastify

Server-Side Development with Fastify — Error Handler, Route List, and Parsing Content

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.

Set Error Handler

We can call setErrorHandler to ad an error handler.

For example, we can write:

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

fastify.setErrorHandler(function (error, request, reply) {
  this.log.error(error)
  reply.status(409).send({ ok: false })
})

fastify.get('/', async (request, reply) => {
  return 'hello world'
})

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

to call setErrorHandler with our handler.

We can get the status code with the error.statusCode property:

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

fastify.setErrorHandler(function (error, request, reply) {
  const { statusCode } = error.statusCode
  if (statusCode >= 500) {
    this.log.error(error)
  } else if (statusCode >= 400) {
    this.log.info(error)
  } else {
    this.log.error(error)
  }
  reply.status(409).send({ ok: false })
})

fastify.get('/', async (request, reply) => {
  return 'hello world'
})

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

We check the statusCode in our error handler.

Print a List of Routes

We can print the list of routes with the fastify.printRoutes method.

For instance, we can write:

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

fastify.ready(() => {
  console.log(fastify.printRoutes())
})

fastify.get('/', async (request, reply) => {
  return '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 print the listen of routes in the fastify.ready handler.

We get:

└── / (GET)

because we defined the / route with get .

Content-Type Parser

We can add our own content type parser with the fastify.addContentTypeParser method.

For example, we can write:

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

fastify.addContentTypeParser('text/json', { asString: true }, fastify.getDefaultJsonParser('ignore', 'ignore'))

fastify.get('/', async (request, reply) => {
  return '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 a content type handler for the text/json MIME-type.

Error Handler

We can add an error handler with the fastify.errorHandler method.

For example, we can write:

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

fastify.get('/', {
  errorHandler: (error, request, reply) => {
    if (error.code === 'SOMETHING_SPECIFIC') {
      reply.send({ custom: 'response' })
      return
    }
    fastify.errorHandler(error, request, response)
  }
}, (request, reply) => {
  return '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 our / route with an error handler.

We check the error.code property to check for the type of error raised.

Then we call reply.send to return the response when an error occurred.

Conclusion

We can set an error handler, content type parser, and print a list of routes with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Reply Serializer, Route Prefix, and Not Found Handler

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.

Close the Server

We can call fastify.close to close the server.

For example, we can write:

const fastify = require('fastify')()

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

fastify.ready(err => {
  if (err) throw err
})

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

to call fastify.close without an argument, which will return a promise.

Route Prefix

We can add a route prefix to our tours with the prefix option.

For instance, we can write:

const fastify = require('fastify')()

fastify.register(function (instance, opts, done) {
  instance.get('/foo', function (request, reply) {
    request.log.info('prefix: %s', instance.prefix)
    reply.send({ prefix: instance.prefix })
  })

  instance.register(function (instance, opts, done) {
    instance.get('/bar', function (request, reply) {
      request.log.info('prefix: %s', instance.prefix)
      reply.send({ prefix: instance.prefix })
    })

    done()
  }, { prefix: '/v2' })

  done()
}, { prefix: '/v1' })

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 go to /v1/v2/bar , we see:

{
  "prefix": "/v1/v2"
}

returned. And if we go to /v1/foo , we see:

{
  "prefix": "/v1"
}

Set Reply Serializer

We can call the setReplySerializer method to set the reply serializer for all routes.

For instance, we can write:

const fastify = require('fastify')()

fastify.setReplySerializer(function (payload, statusCode){
  return `status: ${statusCode}, content: ${payload}`
})

fastify.get('/', async (request, reply) => {
  return 'hello world'
})

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

to add the serializer into our app.

Set Not Found Handler

We can set a handler for handler 404 errors.

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('/', async (request, reply) => {
  return 'hello world'
})

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

to call setNotFoundHandler on the server instance.

We can also set the not found handler on a group of URLs.

For instance, we can write:

const fastify = require('fastify')()

fastify.register(function (instance, options, done) {
  instance.setNotFoundHandler(function (request, reply) {
    return reply.send('not found')
  })
  done()
}, { prefix: '/v1' })

fastify.get('/', async (request, reply) => {
  return 'hello world'
})

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

to add a not found handler for the routes that starts with /v1 .

Conclusion

We can close the server, set route prefixes, set not found handlers, and set a reply serializer with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Rewrite URL and Loading Events

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.

Rewrite URL

We can rewrite URLs with the rewriteUrl option.

For example, we can write:

const fastify = require('fastify')({
  rewriteUrl (req) {
    return req.url === '/hi' ? '/hello' : req.url;
  }
})

fastify.get('/hello', (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 when we go to /hi , the /hello route is invoked.

Server Methods

fastify.server is the Node core server object returned by the Fastify factory function.

The after method lets us run code after current plugin and all the plugins registered within it have finished loading.

For example, we can write:

const fastify = require('fastify')()
fastify
  .register((instance, opts, done) => {
    console.log('Current plugin')
    done()
  })
  .after(err => {
    console.log('After current plugin')
  })
  .register((instance, opts, done) => {
    console.log('Next plugin')
    done()
  })
  .ready(err => {
    console.log('Everything has been loaded')
  })

fastify.get('/', (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 we see:

Current plugin
After current plugin
Next plugin
Everything has been loaded

logged in the console.

If after is called without a function, then it returns a promise.

For instance, we can write:

(async ()=>{
  const fastify = require('fastify')()
  fastify.get('/', (request, reply) => {
    reply.send('hello world')
  })

  await fastify.register(async (instance, opts) => {
    console.log('Current plugin')
  })

  await fastify.after()
  console.log('After current plugin')

  await fastify.register(async (instance, opts) => {
    console.log('Next plugin')
  })

  await fastify.ready()

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

We call fastify.register with an async function to load our plugins.

Then we call fastify.after with await to run code after the plugin is loaded.

We can call the fastify.ready to watch when tyhe server is ready to handle requests.

For example, we can write:

const fastify = require('fastify')()

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

fastify.ready(err => {
  if (err) throw err
})

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

to add a callback to it.

We can also make it return a promise if we don’t pass in a callback:

const fastify = require('fastify')()

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

fastify.ready(err => {
  if (err) throw err
})

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

Listen to Requests

We can listen for requests with the fastify.listen method.

For example, we write:

const fastify = require('fastify')()

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

fastify.ready(err => {
  if (err) throw err
})

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

to start listening to requests with fastify.listen .

The first argument is the port to listen to and the 2nd argument is the IP address to listen to.

Conclusion

We can rewrite URLs and listen to various events with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Error Handler and Query String Options

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.

Error Handler and Query String Options

Fastify has lots of server options we can configure.

The trustProxy option lets Fastify know that it’s sitting behind a proxy.

We can set it to an IP address of the proxy server or a boolean.

Also, we can create a function to set the return the proxy IPs to trust.

We can access the ip, ips, hostname and protocol values on the [request](https://medium.com/r/?url=https%3A%2F%2Fwww.fastify.io%2Fdocs%2Flatest%2FRequest) object:

let i = 0
const fastify = require('fastify')({
  genReqId (req) { return i++ }
})

fastify.get('/', (request, reply) => {
  console.log(request.ip)
  console.log(request.ips)
  console.log(request.hostname)
  console.log(request.protocol)
  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()

ip has the IP address. hostname has the hostname, protocol has the protocol.

The querystringParser option lets us set the query string parser we want to use.

For example, we can write:

const qs = require('qs')
const fastify = require('fastify')({
  querystringParser: str => qs.parse(str)
})
fastify.get('/', (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 use the qs.parse method to parse query strings to an object.

The versioning option lets us version our routes with semver versioning.

We can set this option by writing:

const versioning = {
  storage () {
    let versions = {}
    return {
      get: (version) => { return versions[version] || null },
      set: (version, store) => { versions[version] = store },
      del: (version) => { delete versions[version] },
      empty: () => { versions = {} }
    }
  },
  deriveVersion: (req, ctx) => {
    return req.headers['accept']
  }
}

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

fastify.get('/', (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 get , set , and del methods to add the manipulate the versions object to work with the versions.

frameworkErrors lets us provide error handlers for errors our app encounters.

For example, we can write:

const fastify = require('fastify')({
  frameworkErrors (error, req, res) {
    if (error instanceof FST_ERR_BAD_URL) {
      res.code(400)
      return res.send("Provided url is not valid")
    } else {
      res.send(err)
    }
  }
})

fastify.get('/', (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 our own handler for the bad URL error.

And we return a 400 error if we encounter it.

The clientErrorHandler lets us listen to error events emitted by the client connection and returns a 400 error.

For instance, we can write:

const fastify = require('fastify')({
  clientErrorHandler (err, socket) {
    const body = JSON.stringify({
      error: {
        message: 'Client error',
        code: '400'
      }
    })

this.log.trace({ err }, 'client error')
    socket.end(`HTTP/1.1 400 Bad RequestrnContent-Length: ${body.length}rnContent-Type: application/jsonrnrn${body}`)
  }
})

fastify.get('/', (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 our own clientErrorHandler to add client-side errors.

Conclusion

We can add various handlers to our Fasttify app.