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.

Categories
Fastify

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

Server Options

We can set some options when we require fastify .

http2 lets us use HTTP/2 to bind the socket.

https lets ys listen for TLS connections.

connectionTimeout lets us define the server timeout in milliseconds.

keepAliveTimeout lets us define the server keep-alive timeout in milliseconds.

ignoreTrailingSlash makes Fastify ignore trailing slashes when matching routes.

For example, if we have:

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

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

// registers both "/bar" and "/bar/"
fastify.get('/bar', function(req, reply) {
  reply.send('bar')
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

The /foo and /foo/ matches the /foo/ route.

And /bar and /bar/ matches the /bar/ route.

maxParamLength sets the max length of the route parameter. Anything longer than the length won’t be invoked.

bodyLimit sets the max request body size.

logger lets us enable the logger.

We can add pino as a custom logger by writing:

const pino = require('pino')();

const customLogger = {
  info(o, ...n) { },
  warn(o, ...n) { },
  error(o, ...n) { },
  fatal(o, ...n) { },
  trace(o, ...n) { },
  debug(o, ...n) { },
  child() {
    const child = Object.create(this);
    child.pino = pino.child(...arguments);
    return child;
  },
};

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

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

// registers both "/bar" and "/bar/"
fastify.get('/bar', function(req, reply) {
  reply.send('bar')
})
const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

disableRequestLogging lets us disable logging of requests.

serverFactory lets us pass a custom HTTP server to Fastify.

For example, we can write:

const Fastify = require('fastify')
const http = require('http')

const serverFactory = (handler, opts) => {
  const server = http.createServer((req, res) => {
    handler(req, res)
  })
  return server
}

const fastify = Fastify({ serverFactory })

fastify.get('/', (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 create a server with the http module.

caseSensitive lets us set whether to match routes in a case sensitive manner.

For example, if we have:

const fastify = require('fastify')({
  caseSensitive: false
})

fastify.get('/user/:username', (request, reply) => {
  reply.send(request.params.username)
})

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

Then if we go to /USER/NODEJS , we see NODEJS since caseSensitive is false .

The genReqId option lets us generate the request ID with our own function,.

For example, we can write:

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

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 create our own function to generate the request ID.

Conclusion

We can configure a few options with our Fastify server.

Categories
Fastify

Getting Started with Server-Side Development with Fastify

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.

Installation

We install the fastify package by running:

npm i fastify --save

with NPM or:

yarn add fastify

with Yarn.

First App

We create a simple server app with a few lines of code.

For example, we can write:

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

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

fastify.listen(3000, '0.0.0.0', (err, address) => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

to create a simple server with the / route.

logger set to true enables the built-in logger.

reply.send sends a response.

fastify.listen starts the app and listens to the server.

The first argument is the port, the 2nd argument is the IP address to listen for requests from. '0.0.0.0' listens to all addresses.

When we go to / , we see:

{"hello":"world"}

returned.

We can also use astbc and await in our router handlers.

For example, we can write:

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

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 just return the object we want as the response.

And we call fastify.listen without the callback.

Validate Request Data

We can validate request data by passing in an option to our route.

For example, we can write:

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

const opts = {
  schema: {
    body: {
      type: 'object',
      properties: {
        foo: { type: 'string' },
        bar: { type: 'number' }
      }
    }
  }
}

fastify.post('/', opts, 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 create our schema and then pass in the properties into it.

We specify the types of the foo and bar properties.

This lets us specify the properties that are accepted.

Serializing Data

We can also specify the schema for the response.

For example, we can write:

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

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

fastify.post('/', opts, 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 specify the response that’s returned by the / route.

Run Our Server

We can run our Fastify app with the Fastify CLI.

We install it by running:

npm i fastify-cli

Then in package.json , we add:

{
  "scripts": {
    "start": "fastify start server.js"
  }
}

Then we run it with:

npm start

Conclusion

We can create a simple back end Node web app easily with Fastify.

Categories
Fastify

Add Basic Authentication to Our Fastify App with fastify-basic-auth

With the fastify-basic-auth library, we can add basic authentication to our Fastify app quickly.

In this article, we’ll look at how to use the library to add authentication to our Fastify app.

Installation

We can install the package by running:

npm i fastify-basic-auth

Adding Basic Auth

We can add basic auth to our Fastify app by writing some code.

For example, we can write:

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

const authenticate = {realm: 'Westeros'}
fastify.register(require('fastify-basic-auth'), { validate, authenticate })

function validate (username, password, req, reply, done) {
  if (username === 'foo' && password === 'bar') {
    done()
  } else {
    done(new Error('invalid user'))
  }
}

fastify.after(() => {
  fastify.addHook('onRequest', fastify.basicAuth)

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

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We register the fastify-basic-auth plugin with validate and authenticate .

validate is a function to validate the username and password.

authenticate is an object to set the realm.

To add basic auth, we called addHook to add a hook that checks the username and password with validate on each request.

Any routes that are registered in the after hook have protection with basic auth.

The validate function can be async.

For example, we can write:

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

const authenticate = {realm: 'Westeros'}
fastify.register(require('fastify-basic-auth'), { validate, authenticate })

async function validate (username, password, req, reply) {
  if (username !== 'foo' || password !== 'bar') {
    return new Error('invalid user')
  }
}

fastify.after(() => {
  fastify.addHook('onRequest', fastify.basicAuth)

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

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

If validate is async, then we don’t need to call done .

Also, we can use it with the onRequest property:

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

const authenticate = { realm: 'Westeros' }
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
async function validate (username, password, req, reply) {
  if (username !== 'foo' || password !== 'bar') {
    return new Error('invalid user')
  }
}

fastify.after(() => {
  fastify.route({
    method: 'GET',
    url: '/',
    onRequest: fastify.basicAuth,
    handler: async (req, reply) => {
      return { hello: 'world' }
    }
  })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We set fastify.basicAuth as the value of onRequest to add basic auth to our GET / route.

Also, we can use it with fastify-auth :

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

const authenticate = {realm: 'Westeros'}
fastify.register(require('fastify-auth'))
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
async function validate (username, password, req, reply) {
  if (username !== 'foo' || password !== 'bar') {
    return new Error('invalid user')
  }
}

fastify.after(() => {
  fastify.addHook('preHandler', fastify.auth([fastify.basicAuth]))

fastify.route({
    method: 'GET',
    url: '/',
    onRequest: fastify.auth([fastify.basicAuth]),
    handler: async (req, reply) => {
      return { hello: 'world' }
    }
  })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We register the basic auth handler in the adfter hook and in the onRequest property of the GET / route.

Conclusion

The fastify-basic-auth library lets us add basic auth to our Fastify app with a few lines of code.

Categories
Fastify

Add Authentication to Our Fastify App with fastify-auth

With the fastify-auth library, we can add authentication to our Fastify app quickly.

In this article, we’ll look at how to use the library to add authentication to our Fastify app.

Install

We can install the library by running:

npm i fastify-auth

Add Authentication

We can add authentication to our app by writing:

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

fastify
  .decorate('verifyJWTandLevel',  (request, reply, done) => {
    done()
  })
  .decorate('verifyUserAndPassword', (request, reply, done) => {
    console.log(request, reply)
    done()
  })
  .register(require('fastify-auth'))
  .after(() => {
    fastify.route({
      method: 'POST',
      url: '/auth-multiple',
      preHandler: fastify.auth([
        fastify.verifyJWTandLevel,
        fastify.verifyUserAndPassword
      ]),
      handler: (req, reply) => {
        req.log.info('Auth route')
        reply.send({ hello: 'world' })
      }
    })
  })

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

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We call decorate to add our authentication handlers to our app.

The callback has the request with the request data.

reply lets us set the response.

done is a function we call to call the next middleware.

If there’s an error, we pass in an Error instance to the done function.

We call the register method to register the fasify-auth plugin.

When we make a POST request to the auth-multiple route, we see the request and reply logged.

The routes that are registered in the after callback will have the auth handlers available.

They’re run since we set them to the preHandler property to add the array of auth handlers to run.

The default relationship is an OR relationship.

If we want both handlers to be valid before running the route handler, then we write:

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

fastify
  .decorate('verifyJWTandLevel',  (request, reply, done) => {
    console.log(request, reply)
    done()
  })
  .decorate('verifyUserAndPassword', (request, reply, done) => {
    console.log(request, reply)
    done()
  })
  .register(require('fastify-auth'))
  .after(() => {
    fastify.route({
      method: 'POST',
      url: '/auth-multiple',
      preHandler: fastify.auth([
        fastify.verifyJWTandLevel,
        fastify.verifyUserAndPassword
      ], {
        relation: 'and'
      }),
      handler: (req, reply) => {
        req.log.info('Auth route')
        reply.send({ hello: 'world' })
      }
    })
  })

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

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

Fastify-auth supports promises returned by the functions.

So we can use async and await in our auth handler callbacks:

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

fastify
  .decorate('asyncVerifyJWTandLevel', async function (request, reply) {
    console.log('asyncVerifyJWTandLevel');
    await validation()
  })
  .decorate('asyncVerifyUserAndPassword', function (request, reply) {
    console.log('asyncVerifyUserAndPassword');
    return Promise.resolve()
  })
  .register(require('fastify-auth'))
  .after(() => {
    fastify.route({
      method: 'POST',
      url: '/auth-multiple',
      preHandler: fastify.auth([
        fastify.asyncVerifyJWTandLevel,
        fastify.asyncVerifyUserAndPassword
      ]),
      handler: (req, reply) => {
        req.log.info('Auth route')
        reply.send({ hello: 'world' })
      }
    })
  })

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

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We use async and await with the asyncVerifyJWTandLevel handler.

And we return a promise within the asyncVerifyUserAndPassword handler.

Conclusion

We can add our own auth logic easily to our Fastify app with the fastify-auth plugin.