mpen
mpen

Reputation: 283043

Fastify: How to have public and private routes?

I read the docs, but I still don't understand how I'm supposed to split my routes so that only some plugins apply to them.

Here's what I've got:

index.ts

import fastify from 'fastify'
import bookingsRoutes from './routes/bookings'

const server = fastify({
    logger: process.env.NODE_ENV !== 'production',
    trustProxy: '64.225.88.57', // DigitalOcean load balancer
})


server.get('/health', (_, reply) => {
    reply.send("OK")
})

server.register(bookingsRoutes)

server.listen(process.env.PORT || 3000, '0.0.0.0', (err, address) => {
    if (err) {
        server.log.error(err)
        process.exit(1)
    }
    server.log.info(`Server listening at ${address}`)
})

bookings.ts

// ...

const plugin: FastifyPluginAsync = async api => {
    await Promise.all([
        api.register(dbPlugin),
        api.register(authPlugin),
    ])
    const {db,log} = api

    api.route<{Body:BookingRequestType,Reply:BookingType}>({
        url: '/bookings',
        method: 'POST',
        async handler(req, res) {
            console.log('got user',req.user)
        }
    })
}

export default fp(plugin, '3.x')

I thought by registering dbPlugin and authPlugin inside the booking routes plugin that it would only apply to those routes, but that doesn't seem to be right. It seems to be applying to /health too.

Also not sure if I should be awaiting the 2 register functions like that but they return promises and that seems to be the only way to get the db object back out of them...

What's the proper way to do this?

Upvotes: 2

Views: 3370

Answers (1)

Manuel Spigolon
Manuel Spigolon

Reputation: 12900

Start from this snippet:

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

fastify.register(async function plugin (privatePlugin, opts) {
  privatePlugin.addHook('onRequest', function hook (request, reply, done) {
    if (request.headers['x-auth'] === '123') {
      done()
    } else {
      done(new Error('wrong auth'))
    }
  })

  privatePlugin.get('/', async (request, reply) => {
    return { private: 'private' }
  })
})

fastify.register(async function plugin (publicPlugin, opts) {
  publicPlugin.get('/', async (request, reply) => {
    return { public: 'public' }
  })
})

fastify.listen(8080)

This shows how to create a:

  • private context and
  • a public context

all the routes registered to the privatePlugin will inherit the onRequest hook - the authentication check.

The publicPlugin will not because it is a privatePlugin's sibling. Read here for more detail

Why isn't fastify smart enough to tell which plugin has already been included so that I can list all the dependencies explicitly?

Because in Fastify you can register multiple times the same plugins with different configurations but you must check if:

  • the context has already it or not: registering twice a plugin slow down the server, consume memory etc...
  • the plugin may support multiple registrations in the same context (like fastify-mongodb does within the namespace configuration)

It is a design choice, otherwise, it would be much more confusing in complex applications.


Edit after comments:

The auth plugin should focus on authorization only and it should not care about the database.

In this case I would change your code like so:

// auth-plugin.js
const fp = require("fastify-plugin");

const plugin = async (api, options) => {
  api.addHook("preHandler", async (req, reply) => {
    if (!req.user) {
      throw new Error("Must be logged in");
    }
  });
};

module.exports = fp(plugin, {
  name: 'org-auth-plugin',
  dependencies: ['foo-db-plugin']
})

and

const fp = require("fastify-plugin");

const plugin = async (api, options) => {
  api.decorate("db", "I'm a database");
  // TODO: create/connect to database
};

module.exports = fp(plugin, {
  name: 'foo-db-plugin'
})

In this way you will be warned during the startup that your org-auth-plugin needs the db plugin to work correctly.

Upvotes: 4

Related Questions