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

@lepresk/next-bff-fetch

v0.1.0

Published

Server-only Backend-for-Frontend fetch client for the Next.js App Router: forwards session cookies to an upstream API, re-emits Set-Cookie on the Next origin, negotiates JSON content-type, and degrades gracefully when the upstream is unreachable.

Readme

@lepresk/next-bff-fetch

CI npm version node license: MIT

A server-only fetch client for the Next.js App Router that implements the Backend-for-Frontend (BFF) pattern: the browser talks only to your Next.js server, and the Next.js server talks to your API.

The browser never holds an API token and never sends a cross-origin request, so the session cookie stays HttpOnly and you do not need CORS.

What it does

createApiFetch returns an apiFetch(path, init) function that, on every call:

  • reads the session cookie from the incoming Next.js request and forwards it to the upstream API as a Cookie header;
  • sets Content-Type: application/json only when appropriate, and never for FormData, Blob, or URLSearchParams;
  • returns a plain, typed result (status, parsed body, setCookieHeaders, retryAfterSeconds) instead of a raw Response;
  • returns a controlled 503 when the API is unreachable, instead of throwing an unhandled transport error.

Companion helpers re-emit the API's Set-Cookie headers onto the Next.js origin, which is what makes login, logout, and session refresh work through the BFF.

The entry point imports server-only, so importing it from a Client Component fails the build by design.

Install

pnpm add @lepresk/next-bff-fetch

next is a peer dependency (>=15, App Router with async cookies() and headers()).

Quick start

Create the client once, from environment configuration:

// lib/api.ts
import { createApiFetch } from '@lepresk/next-bff-fetch';

export const apiFetch = createApiFetch({
  apiInternalUrl: process.env.API_INTERNAL_URL ?? 'http://localhost:3001/api/v1',
  sessionCookieName: 'app_session',
});

Read data from a Server Component or Server Action:

'use server';

import { apiFetch } from '@/lib/api';

export async function getProfile() {
  const res = await apiFetch('/auth/me');
  if (res.status !== 200) {
    return { ok: false as const, status: res.status };
  }
  return { ok: true as const, profile: res.body };
}

Persist the session on login and clear it on logout:

'use server';

import { apiFetch } from '@/lib/api';
import { applySessionCookieFromApi, clearSessionCookie } from '@lepresk/next-bff-fetch';

const SESSION_COOKIE = 'app_session';

export async function login(email: string, password: string) {
  const res = await apiFetch('/auth/login', {
    method: 'POST',
    body: JSON.stringify({ email, password }),
  });

  if (res.status === 200) {
    await applySessionCookieFromApi(res.setCookieHeaders, {
      sessionCookieName: SESSION_COOKIE,
      maxAgeSeconds: 60 * 60 * 24 * 30,
    });
  }

  return res;
}

export async function logout() {
  await apiFetch('/auth/logout', { method: 'POST' });
  await clearSessionCookie(SESSION_COOKIE);
}

API

createApiFetch(config): (path, init?) => Promise<ApiFetchResult>

| Config field | Type | Description | | -------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------ | | apiInternalUrl | string | Base URL of the upstream API. A trailing slash is normalized. | | sessionCookieName | string | Cookie read from the Next.js request and forwarded as the Cookie header. | | buildForwardHeaders? | () => Record<string, string> \| Promise<...> | Extra headers to forward on every request (for example the real client IP). | | upstreamUnavailableBody? | unknown | Body returned with 503 when the API is unreachable. Has a sensible default. |

interface ApiFetchResult {
  readonly status: number;
  readonly body: unknown; // parsed JSON, { message: string } for non-JSON, or null
  readonly rawText: string;
  readonly setCookieHeaders: readonly string[];
  readonly retryAfterSeconds: number | undefined; // parsed from the Retry-After header
}

Requests are always sent with cache: 'no-store'.

Session cookie helpers

  • applySessionSetCookies(setCookieHeaders, options) re-emits every matching session cookie onto the Next.js origin.
  • applySessionCookieFromApi(setCookieHeaders, options) is a login helper that throws when no session cookie is present.
  • clearSessionCookie(name, path?) deletes the cookie on the Next.js origin.

ApplySessionCookiesOptions: { sessionCookieName, cookiePath?, secureInProduction?, maxAgeSeconds? }. Pass maxAgeSeconds: null to write a browser-session cookie (no Max-Age).

Low-level utilities

  • parseSetCookieHeader(header) parses one Set-Cookie header into { name, value, maxAge, path, sameSite, secure, httpOnly }.
  • shouldAttachJsonContentType(body, headers) is the content-type negotiation predicate.
  • isUpstreamNetworkError(error) distinguishes a fetch transport failure from an HTTP error response.

Forwarding the real client IP and user agent

buildForwardHeaders runs per request. Read headers() from next/headers and return whatever trusted metadata your API expects:

import { createApiFetch } from '@lepresk/next-bff-fetch';
import { headers } from 'next/headers';

export const apiFetch = createApiFetch({
  apiInternalUrl: process.env.API_INTERNAL_URL ?? '',
  sessionCookieName: 'app_session',
  buildForwardHeaders: async () => {
    const incoming = await headers();
    const forwarded: Record<string, string> = {};
    const ip = incoming.get('x-forwarded-for')?.split(',')[0]?.trim();
    if (ip) forwarded['x-client-ip'] = ip;
    const ua = incoming.get('user-agent');
    if (ua) forwarded['x-client-user-agent'] = ua;
    return forwarded;
  },
});

Requirements

  • Next.js >=15 (App Router, async cookies() and headers())
  • Node.js >=18.18

Development

pnpm install
pnpm typecheck
pnpm lint
pnpm test:coverage
pnpm build

The same steps run in CI on every push and pull request against main, across Node 20 and 22. A tag matching v* triggers a signed npm publish with provenance.

License

MIT