Categories
Fastify

Server-Side Development with Fastify — Async and Await and Requests

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 and Promises

We can use async functions with our route handlers and send responses with it.

For example, we can write:

const fastify = require('fastify')({})
const { promisify } = require('util');
const delay = promisify(setTimeout)

fastify.get('/', async function (request, reply) {
  await delay(200)
  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 use an async function as our route handler, so we can await promises.

Then we just return the response body to send the response.

We can throw responses with an Error instance.

For instance, we can write:

const fastify = require('fastify')({})
const { promisify } = require('util');
const delay = promisify(setTimeout)

fastify.get('/', async function (request, reply) {
  const err = new Error()
  err.statusCode = 418
  err.message = 'short and stout'
  throw err
})

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

We can also throw an object by writing:

const fastify = require('fastify')({})
const { promisify } = require('util');
const delay = promisify(setTimeout)

fastify.get('/', async function (request, reply) {
  throw { statusCode: 418, message: 'short and stout' }
})

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

Request

The request object has various properties.

They include:

  • query – the parsed query string
  • body – the body
  • params – the params matching the URL
  • headers – the headers
  • raw – the incoming HTTP request from Node core
  • id – the request ID
  • log – the logger instance of the incoming request
  • ip – the IP address of the incoming request
  • ips – an array of the IP addresses in the X-Forwarded-For header of the incoming request. It’s available only when the trustProxy option is enabled
  • hostname – the hostname of the incoming request
  • protocol – the protocol of the incoming request (https or http)
  • method – the method of the incoming request
  • url – the URL of the incoming request
  • routerMethod – the method defined for the router that is handling the request
  • routerPath – the path pattern defined for the router that is handling the request
  • is404true if the request is being handled by 404 handlers, false if it is not
  • socket – the underlying connection of the incoming request

We can access them by sitting:

const fastify = require('fastify')({})
const { promisify } = require('util');
const delay = promisify(setTimeout)

fastify.post('/:params', options, function (request, reply) {
  console.log(request.body)
  console.log(request.query)
  console.log(request.params)
  console.log(request.headers)
  console.log(request.raw)
  console.log(request.id)
  console.log(request.ip)
  console.log(request.ips)
  console.log(request.hostname)
  console.log(request.protocol)
  request.log.info('some info')
})

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 and get request data with Fastify.

Categories
Fastify

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

Strings Responses

We can send a string response with the reply.send method:

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

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

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

Stream Response

We can send a stream response with reply.send .

For instance, we can write:

index.js

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

fastify.get('/', function(req, reply) {
  const fs = require('fs')
  const stream = fs.createReadStream('some-file', 'utf8')
  reply.send(stream)
})

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

some-file

foo

Then we see foo as our response when we go to the / route.

Buffer Response

We can send a buffer response with the fs.readFile method.

For example, we can write:

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

fastify.get('/', function(req, reply) {
  fs.readFile('some-file', (err, fileBuffer) => {
    reply.send(err || fileBuffer)
  })
})

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 buffer response in the fs.readFile callback.

Errors

We can send errors with the http-errors library.

For instance, we can write:

const fastify = require('fastify')({})
const httpErrors = require('http-errors')

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

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 410 HTTP gone status code.

We can also add an error handler to check for the type of error raised and then send a response.

For instance, we can write:

const fastify = require('fastify')({})
const httpErrors = require('http-errors')

fastify.setErrorHandler(function (error, request, reply) {
  request.log.warn(error)
  const statusCode = error.statusCode >= 400 ? error.statusCode : 500
  reply
    .code(statusCode)
    .type('text/plain')
    .send(statusCode >= 500 ? 'Internal server error' : error.message)
})

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

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 error status code with the error.statusCode property.

Then we call reply.code to set the status code.

type sets the content type. send sends the response with the content of the argument.

We can also write:

const fastify = require('fastify')({})
const httpErrors = require('http-errors')

fastify.setNotFoundHandler(function (request, reply) {
  reply
    .code(404)
    .type('text/plain')
    .send('a custom 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()

to send the response.

We call callNotFound to call the not found handler.

Conclusion

We can send various kinds of HTTP responses with Fastify.

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.