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

fastify-query

v0.4.0

Published

Fastify plugin for QUERY routes with JSONPath and JSON Pointer filtering

Downloads

278

Readme

fastify-query

CI npm

A Fastify plugin that automatically adds QUERY method handlers with JSONPath and JSON Pointer filtering.

Its main purpose is to be a drop-in solution for servers that already serve large responses to GET requests and modern clients who only need specific parts of these responses.

Clients can send a query expression in the request body (using Content-Type: application/jsonpath or application/jsonpointer) and receive a filtered version of the original response payload.

Where/why is it needed?

Any API where the server provides arrays and objects with a lot of data, while the client has specific purpose and needs only specific entries. Which is almost any API, depending on how you define "a lot".

For example, take a look at GitHub's REST API endpoints for repositories. Scroll through example responses and responses schemas. Have you ever needed all of these? And how often do you need less than 5 scalar fields? The gh utility has builtin --jq option for a reason. However, just like | jq pipelines or any other further processing, this is purely client-side: the network traffic is still bloated with unused data, server still serializes whole thing and client still deserializes whole thing.

In the typical usecase, the server doesn't care too much. It runs on a cluster of some Xeon Diamond 9000, has terabytes of RAM, multiple layers of cache, and gigantic uplinks. But for clients, it's noticeable amount of wasted memory and network resources.

Before HTTP QUERY became a thing, there was no well-standardised, flexible way to achieve this. Nowadays, hopefully this package shows how easy and convenient can it be.

If you are implementing QUERY-based filtering on your server to process it immediately and lower resource consumption by retrieving only specific data, this package can be useful as well: use it to enable QUERY support globally, and then gradually populate excludeRequest option with endpoints that have your internal business logic updated.

Installation

npm i fastify-query

Usage

// server
import Fastify from 'fastify';
import fastifyQuery from 'fastify-query';

const app = Fastify();

await app.register(fastifyQuery); // this does the trick!

app.get('/users', async () => {
  return [
    { id: 1, name: 'Alice', role: 'admin' },
    { id: 2, name: 'Bob', role: 'user' },
    { id: 3, name: 'Carol', role: 'user' },
  ];
});

await app.listen({ port: 3000 });
# request example (JSONPath)
curl -X QUERY -H 'Content-Type: application/jsonpath' -d '$[[email protected]=="user"]' http://localhost:3000/users
// response (prettified)
[
  { "id": 2, "name": "Bob", "role": "user" },
  { "id": 3, "name": "Carol", "role": "user" }
]
# request example (JSON Pointer)
curl -X QUERY -H 'Content-Type: application/jsonpointer' -d '/1/name' http://localhost:3000/users
// response
"Bob"

Options

| Option | Type | Default | Description | |------------------------|------------------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------| | addQueryTypes | Record<string, (document, query) => value> | {} | Additional query types to merge on top of defaults (or overrideQueryTypes) | | advertiseAcceptQuery | string[] | ['GET', 'HEAD', 'QUERY'] | HTTP methods on which the Accept-Query header should be set | | baseMethod | string | 'GET' | Original method implementing server logic for the route | | decorateReply | boolean | false | Whether to decorate reply with sendQuery method | | excludeReply | (reply) => boolean | () => false | Whether to not apply the query filter to the response payload | | excludeRequest | boolean \| string \| RegExp \| string[] \| Set \| (route) => boolean | false | Excludes which routes receive a QUERY variant | | exposeQueryRoutes | boolean | true | If false, completely disables creating QUERY routes | | filterReply | (reply) => boolean | status code is 2xx | Whether to apply the query filter to the response payload | | filterRequest | boolean \| string \| RegExp \| string[] \| Set \| (route) => boolean | true | Filters which routes receive a QUERY variant | | overrideQueryTypes | Record<string, (document, query) => value> | defaultQueryTypes | Replaces the default query types entirely | | strict | boolean | null | null | Whether to throw on unknown Content-Type or return unfiltered response |

Default query types

The plugin exports defaultQueryTypes:

import { defaultQueryTypes } from 'fastify-query';

// {
//   'application/jsonpath': queryJsonpath,       // `query` from jsonpath-rfc9535
//   'application/jsonpointer': queryJsonpointer, // `get` from jsonpointer
// }

Keys become the accepted Content-Type values (and are advertised in Accept-Query). Values are the functions that evaluate the query expression against the full response.

reply.sendQuery

Setting decorateReply option to true enables reply.sendQuery(data) method.

This method can be used as direct replacement to reply.send(data) and it implements similar filtering logic as QUERY handlers added by the plugin. The options addQueryTypes, overrideQueryTypes, strict are applied to this method.

filterRequest examples

// All routes (default)
filterRequest: true

// Exact URL
filterRequest: '/users'

// Multiple URLs
filterRequest: ['/users', '/posts']

// Regular expression
filterRequest: /^\/api\//

// Custom function
filterRequest: ({ url }) => url.startsWith('/api') || url.endsWith('.json')

Custom query types examples

import fastifyQuery, { defaultQueryTypes } from 'fastify-query';

// Add a custom content type alongside the defaults
await app.register(fastifyQuery, {
  addQueryTypes: {
    'application/x-jsonpath': defaultQueryTypes['application/jsonpath'],
  },
});

// Replace defaults entirely
await app.register(fastifyQuery, {
  overrideQueryTypes: {
    'application/xpath': (document, query) => {
      // your implementation
    },
  },
});

Strict handling

The strict option defines behaviour in case if no function is defined for the Content-Type provided by client. If it's true, it returns HTTP 415. If it's false, it returns the response without further processing.

If it's undefined or null (default), it relies on handling parameter in Prefer request header. By default it's in strict mode, providing handling=lenient overrides it.

How it works

  1. Registers content-type parsers for each configured query type (query expression is a string).
  2. On every matching route:
    • Adds Accept-Query header listing the supported query content types.
    • Creates a sibling route with QUERY method that:
      • Invokes the original route handler.
      • Applies the query expression (based on Content-Type) to the result.
      • Serializes and returns the filtered result as response.

How it works internally

  • Accept-Query is added using onSend hooks.
  • New QUERY route is registered via onRoute hook.
  • Filtering is done in preSerialization hook using the query function matched by the request's Content-Type.
  • By default, JSONPath filtering uses the jsonpath-rfc9535 package.
  • By default, JSON Pointer filtering uses the jsonpointer package.

Contributing

Contributions made by humans are welcome. This includes contributions made with non-human assistance, as long as the human submitter takes full responsibility: understands the changes to the dot, verified and tested them.

License

Licensed under MIT.