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

@econneq/apollo-next-multitenancy

v1.0.5

Published

Industrial-grade Apollo abstraction for Next.js App Router with multi-tenancy support. Auto-detects server/client context, injects x-tenant-id, and routes through BFF or direct backend.

Readme

@econneq/apollo-next-multitenancy

Industrial-grade Apollo abstraction for Next.js App Router with first-class multi-tenancy.

What it solves

| Pain Point | How this package fixes it | |---|---| | Server vs Client Apollo setup | One factory per context — no boilerplate | | Forgetting x-tenant-id | Injected automatically on every request | | BFF wiring | createBffHandler is a one-liner proxy | | Middleware tenant extraction | createTenantMiddleware with 4 strategies | | Single vs multi-tenant | Config flag — same codebase for both |


Architecture

Browser (Client Component)
    │  useQuery / useMutation  ← x-tenant-id auto-injected
    ▼
/api/graphql  (BFF)
    │  createBffHandler        ← validates auth, enriches headers
    ▼
http://backend:8000/graphql   ← x-tenant-id forwarded

Server Component / Server Action
    │  query() / mutation()    ← x-tenant-id from next/headers
    ▼
http://backend:8000/graphql   ← direct call, no BFF hop

Installation

npm install @econneq/apollo-next-multitenancy \
  @apollo/client \
  @apollo/experimental-nextjs-app-support \
  graphql

or

pnpm add @econneq/apollo-next-multitenancy@latest @apollo/client @apollo/experimental-nextjs-app-support graphql next-auth --filter school-front-core

Quick Start (5 files)

1. Middleware — extract tenant on every request

Option A — Simple (no existing middleware):

Use createTenantMiddleware when your app doesn't already have a custom middleware:

// middleware.ts
import { createTenantMiddleware } from "@econneq/apollo-next-multitenancy/middleware";

export const middleware = createTenantMiddleware({
  strategy: "subdomain",  // acme.app.com → "acme"
  fallback: "default",    // for localhost
});

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

Option B — Custom middleware (already have i18n / URL rewriting):

If you already have a middleware that handles subdomain extraction, locale detection, or URL rewriting — skip createTenantMiddleware entirely. Just stamp x-tenant-id directly inside your existing middleware:

// middleware.ts — inside your existing proxy/router middleware
const domain = extractSubdomain(request.headers.get('host') || '')
const response = NextResponse.rewrite(rewrittenUrl)

// ── Add these lines — that's all the package needs ──
const tenantId = domain !== 'public'
  ? domain
  : process.env.TENANT_ID ?? null

if (tenantId) {
  // Read by BFF (createBffHandler)
  response.headers.set('x-tenant-id', tenantId)
  // Read by Server Components via next/headers()
  response.headers.set('x-middleware-request-x-tenant-id', tenantId)
}
// ────────────────────────────────────────────────────

See examples/with-custom-middleware/middleware.ts for a complete real-world example with subdomain extraction, locale detection, and URL rewriting combined.


2. Server client — one-time setup

// lib/apollo-server.ts
import { createServerApolloClient } from "@econneq/apollo-next-multitenancy/server";

export const { query, mutation } = createServerApolloClient({
  serverUrl: process.env.INTERNAL_GRAPHQL_URL,
  // Option B (custom middleware): no tenant config needed —
  // x-tenant-id is already in next/headers() from your middleware.
  //
  // Option A (createTenantMiddleware): add a resolver:
  // tenant: { resolveTenantId: subdomainTenantResolver },
});

3. BFF route

// app/api/graphql/route.ts
import { createBffHandler } from "@econneq/apollo-next-multitenancy/bff";

const { GET, POST } = createBffHandler({
  upstreamUrl: process.env.INTERNAL_GRAPHQL_URL!,
  // x-tenant-id is forwarded automatically from incoming request headers
});

export { GET, POST };

4. Root layout — wrap with provider

For apps using a [domain] URL segment (from a custom middleware rewrite):

// app/[locale]/[domain]/layout.tsx
import { headers } from "next/headers";
import { ApolloMultitenantProvider } from "@econneq/apollo-next-multitenancy/client";

export default async function DomainLayout({ children, params }) {
  const { domain } = await params;
  const headerStore = await headers();

  // Prefer middleware header (authoritative), fall back to URL segment
  const tenantId = headerStore.get("x-tenant-id") ?? domain;

  return (
    <ApolloMultitenantProvider tenantId={tenantId} clientUrl="/api/graphql">
      {children}
    </ApolloMultitenantProvider>
  );
}

For apps using a flat layout (with createTenantMiddleware):

// app/layout.tsx
import { headers } from "next/headers";
import { ApolloMultitenantProvider } from "@econneq/apollo-next-multitenancy/client";

export default async function RootLayout({ children }) {
  const tenantId = (await headers()).get("x-tenant-id");

  return (
    <html>
      <body>
        <ApolloMultitenantProvider tenantId={tenantId} clientUrl="/api/graphql">
          {children}
        </ApolloMultitenantProvider>
      </body>
    </html>
  );
}

5. Use in Server and Client Components

// app/dashboard/page.tsx (Server Component — direct backend call)
import { query } from "@/lib/apollo-server";
import { gql } from "@apollo/client";

const GET_DASHBOARD = gql`query GetDashboard { dashboard { tenantName plan } }`;

export default async function Page() {
  const { data } = await query(GET_DASHBOARD);
  return <h1>{data.dashboard.tenantName}</h1>;
}
// components/UserList.tsx (Client Component — routes through BFF)
"use client";
import { useQuery } from "@econneq/apollo-next-multitenancy/client";
import { gql } from "@apollo/client";

const GET_USERS = gql`query GetUsers { users { id name } }`;

export function UserList() {
  const { data, loading } = useQuery(GET_USERS); // x-tenant-id injected automatically
  if (loading) return <p>Loading...</p>;
  return <ul>{data?.users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

Tenant Strategies

| Strategy | Use case | Example | |---|---|---| | subdomain | tenant.app.com | SaaS with per-tenant subdomains | | path | /tenant/page | Path-based multi-tenancy | | header | Forward existing header | Behind API gateway | | custom | Any logic | Session, JWT, DB lookup |

Already have a custom middleware? You don't need any of these strategies. Just set x-tenant-id and x-middleware-request-x-tenant-id headers directly as shown in Option B above.

Single-tenant setup

createServerApolloClient({ tenant: { tenantId: "my-company" } });
createBffHandler({ tenant: { tenantId: "my-company" }, upstreamUrl: "..." });
<ApolloMultitenantProvider tenantId="my-company" />;

// Or via env var:
TENANT_ID=my-company

BFF: Auth-gated proxy

// app/api/graphql/route.ts
import { getServerSession } from "next-auth";
import { createBffHandler } from "@econneq/apollo-next-multitenancy/bff";

const { POST, GET } = createBffHandler({
  upstreamUrl: process.env.INTERNAL_GRAPHQL_URL!,

  async onRequest(req) {
    const session = await getServerSession();
    if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 });
  },

  async enrichHeaders(req, headers) {
    const session = await getServerSession();
    return { ...headers, "x-user-id": session?.user?.id ?? "" };
  },
});

Examples

The examples/ folder contains copy-paste ready setups:

| Folder | When to use | |---|---| | examples/with-simple-middleware/ | New app, no existing middleware | | examples/with-custom-middleware/ | Already have a proxy / i18n / rewrite middleware |

The with-custom-middleware example is based on a real production multi-tenant platform with subdomain routing + locale detection. Copy the whole folder as a starting point.


API Reference

/server

| Export | Description | |---|---| | createServerApolloClient(options) | Returns { getClient, query, mutation } |

/client

| Export | Description | |---|---| | ApolloMultitenantProvider | Root layout wrapper | | useQuery(doc, options?) | Drop-in Apollo useQuery with tenant header | | useMutation(doc, options?) | Drop-in Apollo useMutation with tenant header | | useTenantContext() | Access { tenantId, headerName } |

/bff

| Export | Description | |---|---| | createBffHandler(options) | Returns { GET, POST } for Next.js route handlers |

Main entry

| Export | Description | |---|---| | subdomainTenantResolver | Built-in subdomain extractor | | envTenantResolver | Built-in env var resolver | | resolveTenantId | Core resolution logic | | All types | TenantConfig, ApolloMultitenantOptions, etc. |


Environment Variables

| Variable | Used by | Description | |---|---|---| | INTERNAL_GRAPHQL_URL | Server, BFF | Your backend GraphQL URL | | NEXT_PUBLIC_GRAPHQL_URL | Server (fallback) | Public GraphQL URL | | TENANT_ID | All | Fixed tenant ID (single-tenant) | | NEXT_PUBLIC_TENANT_ID | Client (fallback) | Fixed tenant ID (browser-visible) | | NEXT_PUBLIC_ROOT_DOMAIN | Custom middleware | Root domain for subdomain extraction |


License

MIT