npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thzero/library_server_fastify

v0.19.1

Published

An opinionated library of common functionality to bootstrap a Fastify based API application.

Readme

GitHub package.json version David License: MIT

library_server_fastify

An opinionated library of common functionality to bootstrap a Fastify based API application.

Supplies the web layer for @thzero/library_server — boot, middleware, plugins and the routes the framework owns. The services and repositories those routes call live in library_server and its satellite packages.

Requirements

NodeJs

Requires NodeJs version 22+.

Installation

NPM

npm install @thzero/library_server_fastify

Peer dependencies

  • @thzero/library_common
  • @thzero/library_common_service
  • @thzero/library_server

Fastify and its plugins (@fastify/auth, @fastify/compress, @fastify/cors, @fastify/helmet, @fastify/rate-limit, @fastify/routes, @fastify/static) are direct dependencies — you do not install them yourself.

Getting started

FastifyBootMain extends BootMain from library_server and wires Fastify in. An application subclasses it and starts it with its boot plugins:

import BootMain from '@thzero/library_server_fastify/boot/index.js';

class AppBootMain extends BootMain {
    _initServicesLoggers() {
        this._registerServicesLogger(AppConstants.InjectorKeys.SERVICE_LOGGER_PINO, new pinoLoggerService());
    }
}

(async function() {
    await (new AppBootMain()).start(ApiPlugin, NewsApiPlugin, UsersApiPlugin);
})();

Boot extension points

Override these on your FastifyBootMain derived class. Each is called during _initApp with the default options, and returning null disables that concern entirely.

| Method | Controls | |---|---| | _initCompression(options) | @fastify/compress | | _initCors(options) | @fastify/cors | | _initHelmet(options) | @fastify/helmet | | _initRateLimit(options) | @fastify/rate-limit | | _initAuthentication(map) | The authentication middleware registered as authenticationDefault | | _initAuthorization(map) | The authorization middleware registered as authorizationDefault | | _initRoute(route) | Called per route as it is registered | | _initAppListen(app, server, address, port, err) | The listen callback | | _initAppPost(app, args) | After the app is built, before it listens |

The inherited library_server hooks — _initServices, _initRepositories, _initServicesLoggers, _initRoutes, _initCleanup and the rest — apply here too.

Middleware

Authentication — middleware/authentication.js

Registered as authenticationDefault. Reads the bearer token from the Authorization header, verifies it through SERVICE_AUTH, and attaches request.token, request.user and request.claims.

Six header forms are accepted, matching the prefixes the constants declare:

Bearer <token>     bearer <token>     BEARER <token>
Bearer: <token>    bearer: <token>    BEARER: <token>

The prefix must be at the start; surrounding whitespace is trimmed; the remainder is taken whole, so a token containing the prefix again is not truncated.

With required: false a caller presenting no token passes through with no request.user. A caller who does present a token still has it validated, and an invalid one is a 401 either way.

Authorization — middleware/authorization.js

Registered as authorizationDefault. Checks request.user's roles against the route's roles through SERVICE_SECURITY.

With required: false and no request.user, it returns without denying — the route is anonymous-friendly. An authenticated caller on the same route still has their roles checked.

A route that runs authorization but declares no roles denies everyone. That is fail-closed, but it means authorizationDefault without a roles list is always a 401.

Declaring a route's auth

router.post(this._join('/things'),
    {
        preHandler: router.auth([
            router.authenticationDefault,
            router.authorizationDefault
        ],
        {
            relation: LibraryCommonConstants.Security.logicalAnd,
            roles: [ 'thing.create' ]
        }),
    },
    async (request, reply) => { ... }
);
  • relation — how the middleware chain combines. logicalAnd means both must pass.
  • roles — the roles the caller must hold.
  • required: false — added to the options object, marks the route anonymous-friendly as described above.

Omitting preHandler entirely leaves the route fully anonymous — no token is read, so request.user is always undefined. That is deliberate for a few routes (/usageMetrics/tag records pre-sign-in telemetry); make sure it is deliberate for yours.

Plugins

| Plugin | Hook | Purpose | |---|---|---| | plugins/apiKey.js | onRequest | Rejects a request whose api key header does not match the configured key | | plugins/responseTime.js | onRequest + onSend | Sets the X-Response-Time header and logs the elapsed time | | plugins/settings.js | onRequest | Sets request.config, and request.correlationId from the inbound header — this is where the correlationId every log line and response carries comes from | | plugins/usageMetrics.js | onSend | Records a usage metric per response, fire and forget | | plugins/auth.js | | A vendored fork of @fastify/auth providing router.auth(...) |

apiKey and usageMetrics strip the Authorization and api key headers from anything they record, so credentials do not reach the metrics collection.

Routes

Registered by the boot plugins that an application passes to start:

| Route | Path | Auth | |---|---|---| | routes/home.js | / | anonymous | | routes/version.js | /version | as configured | | routes/utility.js | /utility/… | utility role for the privileged endpoints | | routes/plans.js | /plans/… | | | routes/news.js | /news/… | news role for admin endpoints | | routes/users.js | /users/… | user role; lookups allow required: false | | routes/usageMetrics.js | /usageMetrics/listing | admin role | | | /usageMetrics/tag | anonymous by design | | routes/admin/… | /admin/<fragment> | <role>.create, .delete, .search, .update |

Subclass routes/index.js (FastifyBaseRoute) for your own routes. _join(path) applies the route's prefix, _jsonResponse(reply, response) sends the framework's response envelope, and _inject(app, injector, key, name) decorates the router with a service so a handler can reach it.

Rate Limiting

Rate limiting is provided by @fastify/rate-limit.

Defaults

The following defualts are used.

    max: 100,          // maximum requests per timeWindow per IP
    timeWindow: '1 minute'

You can adjust the defaults by overriding the following method in your FastifyBootMain dervived class.

_initRateLimit()

Per-Route Overrides

To apply a stricter limit to a specific route, pass a rateLimit config:

router.post(this._join('/logger'), {
    config: {
        rateLimit: {
            max: 30,
            timeWindow: '1 minute'
        }
    }
}, async (request, reply) => { ... });

Disabling Rate Limiting Globally

To disable rate limiting for the entire application, override _initRateLimit in your FastifyBootMain derived class and return null:

_initRateLimit(options) {
    return null;
}

Disabling Rate Limiting on a Route

To opt a route out of rate limiting entirely (e.g. a health check or catch-all):

router.get(this._join('/'), {
    config: { rateLimit: false }
}, (request, reply) => {
    reply.status(494).send();
});

Development

npm run lint       # eslint .
npm run lint:fix   # eslint . --fix
npm test           # node --test "test/*.test.js"