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.

Categories
Fastify

Server-Side Development with Fastify — Async and Await and Route Prefixes

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.

Async Await

We can use async and await in our route handlers.

For example, we can write:

const fastify = require('fastify')()

const getData = () => Promise.resolve('foo')
const processData = (data) => Promise.resolve(data)

const opts = {
  schema: {
    response: {
      200: {
        type: 'string'
      }
    }
  }
}

fastify.get('/', opts, async function (request, reply) {
  const data = await getData()
  const processed = await processData(data)
  return processed
})

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

We use an async function as the route handler.

then we can use await in the handler to run promises.

We an also use reply.send to send back the data.

For example, we can write:

const fastify = require('fastify')()

const getData = () => Promise.resolve('foo')
const processData = (data) => Promise.resolve(data)

const opts = {
  schema: {
    response: {
      200: {
        type: 'string'
      }
    }
  }
}

fastify.get('/', opts, async function (request, reply) {
  const data = await getData()
  const processed = await processData(data)
  reply.send(processed)
})

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

to call reply.send to send back the response.

We can also await our reply if we’re calling reply.send in an async callback.

For example, we can write:

const fastify = require('fastify')()

const getData = () => Promise.resolve('foo')
const processData = (data) => Promise.resolve(data)

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: {
            type: 'string'
          }
        }
      }
    }
  }
}

fastify.get('/', opts, async function (request, reply) {
  setImmediate(() => {
    reply.send({ hello: 'world' })
  })
  await reply
})

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

to await our reply since we called reply.send in the setImmediate callback.

When using both return value and reply.send(value) at the same time, the first one that happens takes precedence.

If we use async and await with reply.send , then we shouldn’t return any value.

And if we’re using async and await with promises, then we should return the value and don’t use reply.send or return undefined .

Route Prefixing

We can add prefixes to routes.

For example, we can write:

index.js

const fastify = require('fastify')()

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/users.js

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

Then when we go to /v1/user, we see v1 .

And when we go to /v2/user, we see v2 .

Conclusion

We can add async functions as route handlers.

And we can add route prefixes with Fastify.

Categories
Fastify

Server-Side Development with Fastify — Route Declarations

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 Declaration Shorthand

Fastify comes with shorthand methods to declare routes.

The methods include:

  • fastify.get(path, [options], handler)
  • fastify.head(path, [options], handler)
  • fastify.post(path, [options], handler)
  • fastify.put(path, [options], handler)
  • fastify.delete(path, [options], handler)
  • fastify.options(path, [options], handler)
  • fastify.patch(path, [options], handler)

For example, we can write:

const fastify = require('fastify')()

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  }
}
fastify.get('/', opts, (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 create the opts to set the response schema.

Then we pass that into get method as the 2nd argument of it.

We can also write:

const fastify = require('fastify')()

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  },
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
}
fastify.get('/', opts)

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

to put the handler method in the opts object instead of passing it into the 3rd argument.

Url Building

Fastify supports both static and dynamic URLs.

For example, we can register a parametric path by writing:

const fastify = require('fastify')()

fastify.get('/example/:userId', (request, reply) => {
  reply.send('example')
})
fastify.get('/example/:userId/:secretToken', (request, reply) => {
  reply.send('example')
})
fastify.get('/example/*', (request, reply) => {
  reply.send('example')
})

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 routes.

:userId and :secretToken are URL parameters.

And * is a wildcard character.

Also, we can use regex to match URL route patterns.

For example, we can write:

const fastify = require('fastify')()

fastify.get('/example/:file(^d+).png', (request, reply) => {
  reply.send('example')
})

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

We match the URL pattern with the (^d+) pattern.

So when we go to /example/1.png , we see the response returned.

We can define more than one route parameter with the slashes.

For instance, we can write:

const fastify = require('fastify')()

fastify.get('/example/near/:lat-:lng/radius/:r', (request, reply) => {
  reply.send('example')
})

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 :lat-lng and :r route parameters.

And we can do the same with regex:

const fastify = require('fastify')()

fastify.get('/example/at/:hour(^d{2})h:minute(^d{2})m', (request, reply) => {
  reply.send('example')
})

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 declare routes with various match patterns with Fastify.

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.