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

@xenterprises/fastify-xauth-nile

v1.5.0

Published

Fastify plugin for Nile Auth (@niledatabase/server) with multi-instance support, session middleware, and tenant context

Readme

@xenterprises/fastify-xauth-nile

Fastify 5 plugin for Nile Auth built on the @niledatabase/server SDK. It is the Fastify counterpart of @niledatabase/express: it mounts the generated nile-auth routes, wraps each request in nile.withContext({ headers, tenantId }), and exposes session and tenant helpers. Supports multiple named instances. For teams that want Nile's managed multi-tenant auth without hand-rolling request forwarding.

Testing Fastify sample: ../../../sample/nile (Fastify 5 + this plugin, file: local package). Not in nuxt-layers/kitchen-sink — Nile is a mutually exclusive auth provider and needs NILEDB_*. Suite real-API checks also live in fastify-kitchen-sink/.

Install

npm install @xenterprises/fastify-xauth-nile fastify@5

Minimal example

Pass credentials from the Nile console (NILEDB_*) into register() — the plugin never reads process.env itself.

import Fastify from "fastify";
import xAuthNile from "@xenterprises/fastify-xauth-nile";

const app = Fastify();

const superAdmins = (process.env.NILE_SUPERADMINS || "")
  .split(",")
  .map((s) => s.trim())
  .filter(Boolean);

await app.register(xAuthNile, {
  configs: [
    {
      name: "api",
      // nile-auth API URL + database credentials from the Nile console
      apiUrl: process.env.NILEDB_API_URL,
      user: process.env.NILEDB_USER,
      password: process.env.NILEDB_PASSWORD,
      databaseName: process.env.NILEDB_NAME,
      // Required when the frontend is on a different origin than this API
      origin: process.env.NILE_ORIGIN, // e.g. https://app.example.com
      // false only on local HTTP; true for production / staging / unset NODE_ENV
      secureCookies: process.env.NODE_ENV === "development" ? false : true,
      superAdmins,
      protectedPaths: [
        ...(superAdmins.length ? [{ prefix: "/admin", access: "platform" }] : []),
        { prefix: "/portal", access: "session" },
        { prefix: "/affiliates", access: "tenant" },
      ],
    },
  ],
});

// Nile routes are live under the SDK routePrefix (default `/api`):
// GET  /api/auth/session, /api/auth/csrf, /api/me, /api/tenants, ...
// POST /api/signup, /api/auth/signin/:provider, /api/auth/callback/:provider, ...

const auth = app.xAuthNile.default;

app.post("/internal/login", async (request) =>
  auth.signIn(request, { email: request.body.email, password: request.body.password })
);

app.get(
  "/api/private/me",
  { preHandler: auth.requireAuth() },
  async (request) => request.user
);

await app.listen({ port: 3000 });

Options

app.register(xAuthNile, options)options.configs is a required non-empty array; each entry configures one Nile instance.

Required (plugin + SDK)

| Option | Type | Default | Required | Description | |---|---|---|---|---| | name | string | — | yes | Unique instance name (keyed under fastify.xAuthNile.configs) | | apiUrl | string (URL) | — | yes | nile-auth API base URL (NILEDB_API_URL), e.g. https://us-west-2.api.thenile.dev/v2/databases/<db-id> | | user | string | — | yes | Nile database user (NILEDB_USER) — the SDK cannot be constructed without it | | password | string | — | yes | Nile database password (NILEDB_PASSWORD) | | databaseName | string | — | yes | Nile database name (NILEDB_NAME) |

Plugin-owned

These are not passed to Nile().

| Option | Type | Default | Required | Description | |---|---|---|---|---| | basePath | string | — (mount all Nile paths) | no | If set, only mount SDK paths under this prefix (e.g. /api/auth for auth-only). Must be unique per overlapping instance via the resulting routes | | prefix | string | — | no | Shorthand: session-protect this path (access: "session"). Prefer protectedPaths when you have more than one surface | | excludedPaths | array | [] | no | Extra paths excluded from prefix / default path-guard protection | | protectedPaths | array | [] | no | Path surfaces: { prefix, access } where access is platform (superadmin), session (signed-in user), or tenant (session + tenant id) | | superAdmins | string[] | [] | if any path is platform | User ids and/or emails allowed on platform paths | | environment | string | — | no | Optional. If you omit secureCookies, development/dev → insecure cookies. Prefer the ternary on secureCookies instead | | tenantHeader | string | x-tenant-id | no | Header consulted by requireTenant() / withContext() (after URL param, before cookie) | | forwardHeaders | string[] | — | no | Extra request headers forwarded to nile-auth beyond the built-in allowlist (cookie, content-type, authorization, accept, accept-language). Blocked headers (host, nile-origin, …) always stay blocked | | sdkTimeout | number (ms) | — (no timeout) | no | Abandon a stalled nile-auth call after this many ms — forwarded routes reply 504, requireAuth() replies 504 instead of hanging | | cookiePath | string | — (unchanged from Nile) | no | Rewrite or append the Path attribute on outgoing Set-Cookie headers, e.g. cookiePath: "/" when guarding routes outside routePrefix | | stripLocationHeader | boolean | false | no | When true, strips the location header from forwarded Nile responses (e.g. to suppress upstream redirects in direct-API mode) | | verifyTenant | boolean | false | no | Also verify the session user's membership of the resolved tenant (nile.tenants.get under the request context) in requireTenant() and access: "tenant" guards — 403 on mismatch. Costs one SDK query per tenant request; default is format-validation only |

Passed through to Nile()

These match the Nile SDK configuration. Any other non-plugin key is also forwarded.

| Option | Type | Default | Required | Description | |---|---|---|---|---| | routePrefix | string | /api | no | Prefix the SDK applies to generated routes ({routePrefix}/auth/session, {routePrefix}/me, {routePrefix}/signup, {routePrefix}/tenants, …) | | origin | string (URL) | — | no | Frontend origin sent as nile-origin (redirects/cookies). Set this when the UI is on a different host/port than this API. The forwarded Request URL always uses this server's host | | callbackUrl | string | — | no | Override the client-provided auth callback URL | | secureCookies | boolean | true | no | Passed to the SDK. Use process.env.NODE_ENV === "development" ? false : true — not === "production", which would disable cookies when NODE_ENV is unset | | debug | boolean | false | no | Verbose Nile SDK logging | | databaseId | string | derived from apiUrl | no | Nile database id (NILEDB_ID) | | db | object | — | no | pg pool config for tenant-aware DB access (NilePoolConfig) | | headers | object | Headers | — | no | Extra headers sent with every nile-auth API request. Include cookie to forward a session | | tenantId | string | — | no | Fallback tenant id for withContext / requireTenant() when the request has none. Not passed to Nile() (that would be process-wide) | | userId | string | — | no | Not passed to Nile(). Prefer request-scoped session | | extensions | array | — | no | Nile SDK extensions | | routes | object | — | no | Overrides for generated nile-auth paths only (e.g. { SIGNIN: '/v2/auth/signin' }). Not app guards — use protectedPaths for /admin / /portal | | logger | function | — | no | Custom Nile SDK logger | | useLastContext | boolean | — | no | Ignored on the constructor. Every plugin withContext call sets useLastContext: false | | skipHostHeader | boolean | — | no | Skip setting the Host header on nile-auth fetches |

Decorators

fastify.xAuthNile

| Property | Description | |---|---| | get(name) | Return the named instance (or undefined) | | default | The first configured instance | | configs | Record<name, instance> of all instances |

Each instance exposes:

| Property | Signature | Description | |---|---|---| | nile | Server | The raw @niledatabase/server SDK instance (nile.auth, nile.users, nile.tenants, nile.query, nile.db) | | config | object | The merged instance configuration | | getSession(request) | Promise<session \| undefined> | GET /api/auth/session. Do not pass the Fastify request to nile.auth.getSession | | getCsrf(request) | Promise<{ csrfToken }> | GET /api/auth/csrf | | listProviders(request) | Promise<object> | GET /api/auth/providers | | signIn(request, creds) | Promise<user \| Response> | POST /api/auth/signin. Object { email, password } uses the credentials provider. signIn(request, 'google') / signIn(request, 'email', creds) pass a provider name | | signOut(request) | Promise<Response> | POST /api/auth/signout | | signUp(request, payload) | Promise<user \| Response> | POST /api/signup | | forgotPassword(request, payload) | Promise<Response> | POST /api/auth/reset-password (send email) | | resetPassword(request, payload) | Promise<Response> | Complete a password reset | | callback(request, provider) | Promise<Response> | OAuth callback helper | | mfa(request, payload) | Promise<object \| Response> | POST/PUT/DELETE /api/auth/mfa | | refreshSession(request) | Promise<Response> | POST /api/auth/session/token | | getMe(request) | Promise<user \| Response> | GET /api/me — identify the session principal | | withContext(request, fn) | Promise<T> | Run fn(nile) inside a request-scoped Nile context (headers + resolved tenant id) | | requireAuth() | preHandler | 401 unless the request has a valid Nile session; attaches request.auth / request.user | | requireTenant() | preHandler | 400 unless a tenant id resolves (URL param tenantId > tenantHeader > nile.tenant-id cookie); attaches request.tenantId. With verifyTenant: true, also 403s when the session user is not a member | | requireSuperAdmin() | preHandler | 403 unless the session user id or email is in superAdmins. Call after requireAuth() |

Request-scoped

  • request.auth — the full Nile session object (set by requireAuth())
  • request.usersession.user when present (set by requireAuth())
  • request.tenantId — resolved tenant id (set by requireTenant() / access: "tenant"). Presence only, not membership.
  • request.authAccessplatform | session | tenant when a protectedPaths (or prefix) guard matched
  • request.isSuperAdmintrue after a successful platform / requireSuperAdmin() check

Routes

Per instance, every path in nile.paths (plus documented extras the SDK exposes on nile.routes but omits from paths: MFA, verify-email, invites, user-tenants) is registered as GET/POST/PUT/PATCH/DELETE and forwarded to nile.handlers[METHOD] inside nile.withContext({ headers, tenantId }). {param} templates become Fastify :param. Status codes, headers, and set-cookie values are passed through untouched.

Default routePrefix /api therefore serves the full nile-auth surface, including:

| Method | Path | |---|---| | GET | /api/auth/session, /api/auth/csrf, /api/auth/providers, /api/auth/signin, /api/auth/signin/:provider, /api/me, /api/tenants, … | | POST | /api/auth/signin, /api/auth/signin/:provider, /api/auth/session/token, /api/signup (then /api/auth/verify-email when Nile requires verification), /api/auth/callback/:provider, /api/auth/mfa, /api/tenants, … | | PUT | /api/tenants/:tenantId, /api/users, /api/auth/mfa, … | | DELETE | /api/tenants/:tenantId, /api/tenants/:tenantId/users/:userId, /api/auth/mfa, … |

When protectedPaths (or the shorthand prefix) is configured, an onRequest hook applies the matching access mode except on mounted Nile routes and excludedPaths:

| access | Prefix example | Checks | |---|---|---| | platform | /admin | Session + superAdmins allowlist. No tenant required | | session | /portal | Session only | | tenant | /affiliates | Session + tenant id (URL param / header / cookie) |

prefix is a directory guard (/admin and /admin/...). Matching is boundary-aware: /admin does not match /administrator. One-off routes outside these prefixes use preHandler: auth.requireAuth() / requireSuperAdmin() / requireTenant().

SDK routes is not where app surfaces go. That object remaps generated nile-auth paths (SIGNIN, CSRF, ME, …). /admin and /portal belong in protectedPaths. xAuthBetter’s basePath (/api/auth) is this plugin’s routePrefix (/api/api/auth/csrf). Better’s prefix is protectedPaths.

If the frontend posts application/x-www-form-urlencoded (CSRF + credentials), the plugin registers a string parser for that content type unless one already exists (register @fastify/formbody before this plugin if you need parsed objects app-wide).

Error behavior

  • Registration throws on: missing/empty configs, missing name, missing/invalid apiUrl, missing user / password / databaseName, invalid types for SDK options (origin, debug, secureCookies, db, …), duplicate name, overlapping Nile routes across instances, or an SDK instance with no mountable paths. Error messages follow the suite format: xauthnile: missing required option `apiUrl` ... e.g. ...
  • Forwarded routes reply 502 Bad Gateway ({ statusCode: 502, error: "Bad Gateway", message: "xauthnile: nile-auth request failed" }) when the underlying Nile SDK fetch fails, preventing upstream URLs or credentials from leaking into client responses.
  • sdkTimeout makes forwarded routes and requireAuth() reply 504 Gateway Timeout when nile-auth stalls instead of hanging the connection.
  • requireAuth() replies 401 with { statusCode, error, message } when there is no session or session validation fails.
  • requireTenant() replies 400 when no tenant id resolves; with verifyTenant: true, replies 403 when membership check fails.
  • Unsupported methods or unmatched Nile handlers reply 404.

Tenant-aware database access

Use withContext so SDK calls see the request's cookies and tenant id. requireTenant() only resolves an id (URL param > header > cookie); it does not check that the session user belongs to that tenant.

Tenant ids must be A-Za-z0-9_- (Nile UUIDs match). Anything else is ignored so it cannot break the SDK's SET nile.tenant_id statement.

app.get(
  "/api/todos",
  { preHandler: [auth.requireAuth(), auth.requireTenant()] },
  async (request) => {
    return app.xAuthNile.default.withContext(request, async (nile) => {
      const res = await nile.query("select * from todos");
      return res.rows;
    });
  }
);

Pooled connections are released automatically when the Fastify instance closes.

Security notes

  • Rate limiting is your job. The mounted auth surface (/api/auth/signin, /api/signup, /api/auth/reset-password, …) is unauthenticated by design, and a POST /api/signup that Nile refuses with "verification required" triggers a real verification email to the submitted address. Put @fastify/rate-limit (or an edge rule) in front of these routes in production:

    await app.register(import("@fastify/rate-limit"), {
      max: 100,
      timeWindow: "1 minute",
    });
    // registered before xAuthNile so the hooks wrap the auth routes too
  • Tenant ids are client-controlled. requireTenant() validates the format of the id only unless verifyTenant: true is set; without it, any signed-in user can claim any tenant id via URL param, header, or cookie. Authorization then comes from Nile's Postgres RLS under withContext — never trust request.tenantId for access decisions outside SDK queries.

  • Set origin in production. It is the base for verification-email links after signup and is sent to nile-auth as nile-origin. When origin is configured, the client-controlled nile.callback-url cookie is only trusted if it matches that origin; without origin, links fall back to the request's Host header.

  • CORS is not handled. The plugin forwards nile-auth responses without access-control-* headers; add @fastify/cors yourself if a browser client on another origin calls these routes. Preflight OPTIONS is not mounted.

  • rawResponse: true helpers (getSession, signIn, refreshSession, …) return the raw Response — applying its set-cookie headers to the Fastify reply is then your responsibility. Use the exported applyResponseCookies(reply, response) helper (it reads Headers.getSetCookie(); plain Headers.forEach merges multiple cookies into one invalid header).

  • Session cookies across route surfaces: Set cookiePath: "/" if your application guards surfaces outside routePrefix (e.g. /portal or /admin when routePrefix is /api). Nile Auth issues cookies with Path={routePrefix} by default; cookiePath: "/" ensures browsers send session cookies to both /api and out-of-prefix routes without requiring proxy-level cookie rewrites.

Requirements

  • Node.js >= 20 (Node 22+ to run the test suite)
  • Fastify ^5.0.0
  • A Nile workspace + database

Development

npm test        # node --test, offline, Nile SDK mocked
npm run lint    # biome check
npm run format  # biome format --write
npm run typecheck # tsc -p tsconfig.types.json

Consumer env template: .env.example (not published). Copy to .env and run node --env-file=.env examples/basic.js. Real-API checks live in the suite kitchen-sink (fastify-kitchen-sink/), not in this package.