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

@b1-road/nestjs

v0.1.0-alpha.3

Published

Official NestJS toolkit for integrating with Road — BFF auth (OIDC + session + proxy), primitives, decorators, and resource client.

Readme

@b1-road/nestjs

The official NestJS toolkit for integrating with Road. It is a BFF (Backend-for-Frontend): one RoadModule.forRoot() gives you the OIDC login flow, a server-side session + token store, and a streaming proxy to Road API — plus the authorization primitives for your own routes. The browser never holds a JWT; it only carries an opaque, encrypted session cookie.

Status: 0.1.0-alpha.0. BFF-only — there is no Bearer pass-through path. Pairs in lockstep with @b1-road/react (cookie mode) and the b1-road/laravel SDK (a Composer package — no npm scope).

Install

npm install @b1-road/nestjs

Alpha pre-release while the Road API contract is in alpha. The latest dist-tag tracks the newest release, so the install above is all you need.

Peers: @nestjs/common / @nestjs/core (^10 || ^11), reflect-metadata, rxjs, Node 20+. For the production token store, also install the optional peer ioredis.

Quick start (server)

// app.module.ts
import { Module } from '@nestjs/common';
import { RoadModule } from '@b1-road/nestjs';

@Module({
  imports: [RoadModule.forRoot()], // reads the env vars below
})
export class AppModule {}
# .env
AUTH_SERVER_ISSUER_URL=https://auth.example.com
AUTH_SERVER_CLIENT_ID=your-client-id
AUTH_SERVER_CLIENT_SECRET=your-client-secret
AUTH_SERVER_REDIRECT_URI=https://your-app.com/auth/road/callback
SESSION_SECRET=<32+ byte random string>     # node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
REDIS_URL=redis://localhost:6379            # required in production
ROAD_API_BASE_URL=https://api.road.b1.app
# AUTH_SERVER_AUDIENCE=<project-id>         # optional — set only if your Auth Server issues project-scoped (audience'd) tokens

forRoot() mounts everything for you:

| Route | What it does | | --- | --- | | GET /auth/road/login | Starts the OIDC PKCE login; redirects to the Auth Server. Honors ?returnTo=. | | GET /auth/road/callback | Exchanges the code, mints the session, sets the cookie, redirects back. | | POST /auth/road/logout | Revokes the refresh token, destroys the session, clears the cookie. | | ALL /road-api/* | Streaming proxy to Road API — attaches the user's Bearer server-side. |

Auth Server setup: register an OIDC app with the redirect URI https://your-app.com/auth/road/callback and the openid profile email offline_access scopes, then drop its client id/secret into the env. Run npx road doctor to verify the wiring.

Using app.setGlobalPrefix('api')? Supported. Nest applies the prefix to these mounts too, so the proxy is served at /api/road-api/* and the callback at /api/auth/road/callback. Two things follow: point the React SDK's apiBaseUrl at /api/road-api, and register the redirect URI with the prefix (…/api/auth/road/callback) — or exclude auth/road from the global prefix. The module logs this once at boot when it detects a global prefix.

The React companion

@b1-road/react in its cookie-mode default needs two props — no JWT, no authMode:

<RoadProvider
  apiBaseUrl="/road-api"
  onUnauthenticated={() => window.location.assign('/auth/road/login')}
>
  <App />
</RoadProvider>

The React fetcher sends credentials: 'include', omits Authorization, and handles CSRF automatically: the BFF issues a readable XSRF-TOKEN cookie on login and on every proxied response, and the React fetcher echoes it as X-XSRF-TOKEN on mutations. Reads and writes work out of the box.

Controllers — three primitives

auth(), can(), road are the whole controller-author surface. The auth source is the session, not an Authorization header — but the surface is the same one you'd write against any header-based setup, so controllers stay header-agnostic.

import { Controller, Get, Param } from '@nestjs/common';
import { auth, can, Read, Member } from '@b1-road/nestjs';

@Controller('business-units/:buId/members')
export class MembersController {
  @Get()
  list(@Param('buId') buId: string) {
    auth().assert(can(Read, Member).in(buId));
    return auth().road.businessUnits(buId).members.all();
  }
}
const a = auth();
a.userId            // string (Auth Server user id)
a.user              // RoadUser (from the id_token claims)
a.token             // the current access token (refreshed transparently)
a.road              // user-bound RoadClient
a.assert(can(Read, Member).in(buId));

a.road.businessUnits(buId).members        // for await iterable
a.road.as.service().iam.authorize(...)    // outbound service-mode override

Decorators (sugar)

@Get(':buId')
@RequirePermission(Read, Member, { in: 'buId' })   // type-checked
list(@CurrentUser() user: RoadUser) { ... }

@Get('me')
@SkipAuthorization()        // authn still runs, authz skipped
me(@CurrentUser() user: RoadUser) { ... }

@Get('public')
@Public()                   // both skipped
ping() { ... }

How it works

Browser ── session cookie ──▶ NestJS ── Bearer (Auth Server JWT) ──▶ Road API
                              │
                              │ token store (Redis / memory / custom)
                              ▼
                              Auth Server (OIDC discovery + token endpoint)

The browser holds only an iron-session-sealed cookie carrying an opaque session id. The token store, keyed by that id, holds the TokenSet (access + refresh + expiry + cached id_token claims). The proxy and the auth guard fetch the access token from the store, refresh it transparently when it's within 60s of expiry, and attach Authorization: Bearer … server-side. Refresh rotation is invisible to the integrator and the browser.

Configuration

forRoot() with no arguments reads everything from env. Override inline as needed (forRootAsync({...}) exists for ConfigService injection):

RoadModule.forRoot({
  authServer: {
    issuerUrl:    process.env.AUTH_SERVER_ISSUER_URL,
    clientId:     process.env.AUTH_SERVER_CLIENT_ID,
    clientSecret: process.env.AUTH_SERVER_CLIENT_SECRET,
    redirectUri:  process.env.AUTH_SERVER_REDIRECT_URI,
    scopes:       ['openid', 'profile', 'email', 'offline_access'],
    // audience:  process.env.AUTH_SERVER_AUDIENCE,  // optional — project-scoped tokens only
  },
  store:   { driver: 'redis', url: process.env.REDIS_URL },
  session: { name: 'road_session', secret: process.env.SESSION_SECRET, maxAge: 60 * 60 * 24 * 7 },
  proxy:   { prefix: 'road-api', allow: ['organization/*', 'iam/identity/*', 'iam/authorization/*'] },
  api:     { baseUrl: process.env.ROAD_API_BASE_URL, version: 'alpha' },
});

Production safetyforRoot() throws at boot when:

  • OIDC client credentials are missing,
  • store.driver is memory and NODE_ENV === 'production' (use Redis),
  • session.secret is shorter than 32 bytes.

Token store

| Driver | When | Notes | | --- | --- | --- | | memory | dev / tests | Map with TTL eviction (refused in production) | | redis | production | road:session:{id}; needs the optional ioredis peer | | custom | your own | store: { driver: 'custom', store: myStore } implementing RoadTokenStore |

Proxy & CSRF

The proxy forwards only allowlisted path prefixes (default organization/*, iam/identity/*, iam/authorization/*); anything else 404s before any token lookup. Non-GET requests require a double-submit CSRF token (cookie XSRF-TOKEN, header X-XSRF-TOKEN) — the BFF issues the cookie, the React SDK echoes it. Both names are configurable under proxy.csrf.

Test mode — no Auth Server, no Redis, no signed JWTs

import { Test } from '@nestjs/testing';
import { RoadModule } from '@b1-road/nestjs';
import { roadScenario } from '@b1-road/nestjs/testing';

const scenario = roadScenario()
  .withUser('u_owner', { name: 'Eduardo' })
  .withBusinessUnit('bu_1', { name: 'B1' })
  .withRole('bu_1', 'Owner', { permissions: ['*'] })
  .withMember('bu_1', 'u_owner', { roles: ['Owner'] })
  .withSession('u_owner', { sessionId: 'sess_1' });   // pre-authenticated session

const moduleRef = await Test.createTestingModule({
  imports: [RoadModule.forTest(scenario), MembersModule],
}).compile();
const app = moduleRef.createNestApplication();
await app.init();

await request(app.getHttpServer())
  .get('/road-api/organization/business-units/bu_1/members')
  .set('Cookie', await scenario.sessionCookieFor('sess_1'))
  .expect(200);

sessionCookieFor(id) returns a sealed session cookie for a declared session. The OIDC round-trip itself isn't simulated — declare sessions instead.

Service mode (workers, cron, BullMQ)

Outbound calls outside a request use a service JWT:

RoadModule.forRoot({
  service: {
    kind: 'private_key_jwt',
    clientId: process.env.ROAD_SERVICE_CLIENT_ID,
    keyId: process.env.ROAD_SERVICE_KEY_ID,
    privateKey: process.env.ROAD_SERVICE_PRIVATE_KEY,
  },
});

// inside a worker (no request in flight)
constructor(private readonly road: RoadClient) {}
async run() { await this.road.as.service().iam.authorize({ ... }); }

The SDK obtains an Auth Server JWT via client_credentials or private_key_jwt, caches it until exp − 60s, and re-acquires on 401.

CLI

npx road doctor                 # Auth Server + token store + Road API health
npx road session list           # active sessions (requires REDIS_URL)
npx road session revoke <id>    # server-side logout for one session
npx road cache clear            # bust the OIDC discovery cache

Errors

import { RoadAuthzError } from '@b1-road/nestjs';

try {
  await road.iam.authorize({ ... });
} catch (err) {
  if (err instanceof RoadAuthzError) {
    err.code         // 'permission_denied'
    err.decision     // structured DecisionTrace
    err.requestId    // correlates to Road API logs
    err.docs         // https://road.b1.app/errors/permission-denied
  }
}

The BFF adds three RoadAuthnError (401) subclasses: RoadSessionExpiredError (session_expired), RoadRefreshFailedError (refresh_failed), and RoadOidcStateError (oidc_state_mismatch). A missing/expired session yields 401 + WWW-Authenticate: Session, which the React SDK turns into your onUnauthenticated callback. In non-prod, X-Road-Debug: 1 (or ?debug=road) appends the DecisionTrace to 403 bodies.