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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@carlonicora/nextjs-jsonapi

v1.7.2

Published

Next.js JSON:API client with server/client support and caching

Readme

@carlonicora/nextjs-jsonapi

A comprehensive Next.js package providing JSON:API compliant client with unified server/client support, automatic caching, and a complete shadcn/ui component library.

Table of Contents

Features

  • Unified API: Auto-detects environment (server/client) and uses the appropriate request method
  • JSON:API Compliance: Full JSON:API specification support with deserialization and pagination
  • Next.js 16+ Caching: Built-in support for cacheLife() and cacheTag() via cache profiles
  • React Hooks: useJsonApiGet and useJsonApiMutation for client-side data fetching
  • Server Components: Direct server-side data fetching with automatic token handling
  • Multi-Tenant Support: Built-in company ID handling for B2B applications
  • File Uploads: Seamless file upload support with multipart requests
  • shadcn/ui Components: 44 pre-built UI components (41 standard + 3 custom)
  • Utility Functions: cn class merger, mobile detection, and ref composition

Architecture

The library is organized into eight entry points:

@carlonicora/nextjs-jsonapi
├── (main)          # Unified API (auto-detects environment)
├── /core           # Interfaces, factories, registries, and utilities
├── /client         # React hooks and client-side utilities
├── /server         # Server-side requests and caching
├── /permissions    # Permission checking utilities
├── /features       # Built-in feature modules (S3, etc.)
├── /utils          # Utility functions (cn, useIsMobile, etc.)
└── /shadcnui       # 44 shadcn/ui components

Installation

pnpm add @carlonicora/nextjs-jsonapi

Git Submodule Setup (Alternative)

If you want to use the package as a git submodule (for development or before npm release):

1. Add the submodule

cd /path/to/your-project
git submodule add https://github.com/carlonicora/nextjs-jsonapi packages/nextjs-jsonapi

2. Verify it worked

git submodule status
# Should show: <commit-sha> packages/nextjs-jsonapi (heads/master)

3. Commit the submodule

git add .gitmodules packages/nextjs-jsonapi
git commit -m "Add nextjs-jsonapi as submodule"

4. Update your package.json (e.g., apps/web/package.json)

{
  "dependencies": {
    "@carlonicora/nextjs-jsonapi": "workspace:*"
  }
}

5. Ensure pnpm-workspace.yaml includes packages

packages:
  - "apps/*"
  - "packages/*"

6. Install and build

pnpm install
cd packages/nextjs-jsonapi && pnpm build && cd ../..

For CI/CD (GitHub Actions), add submodules: recursive to your checkout step:

- uses: actions/checkout@v4
  with:
    submodules: recursive

Cloning a project with submodules:

# When cloning fresh
git clone --recurse-submodules https://github.com/your/repo.git

# If already cloned
git submodule update --init --recursive

Peer Dependencies

| Package | Version | Required | Purpose | | ----------------- | -------- | -------- | ----------------------------------- | | next | >=14.0.0 | Yes | Next.js framework | | react | >=18.0.0 | Yes | React library | | react-dom | >=18.0.0 | Yes | React DOM | | react-hook-form | >=7.0.0 | Optional | Form handling (for form components) |

Quick Start

1. Configure the API Client

Configure the JSON:API client in your environment setup file:

// src/config/env.ts
import { configureJsonApi } from "@carlonicora/nextjs-jsonapi";
import { bootstrap } from "@/config/Bootstrapper";

configureJsonApi({
  apiUrl: process.env.NEXT_PUBLIC_API_URL!,
  bootstrapper: bootstrap,
});

2. Define Your Modules and Bootstrapper

Create a bootstrapper that registers all modules for both the ModuleRegistry (for Modules.X access) and DataClassRegistry (for JSON:API response translation):

// src/config/Bootstrapper.ts
import { DataClassRegistry, FieldSelector, ModuleRegistry } from "@carlonicora/nextjs-jsonapi/core";
import { ModuleWithPermissions } from "@carlonicora/nextjs-jsonapi/permissions";
import { S3Module } from "@carlonicora/nextjs-jsonapi/features";

// Import your module definitions
import { ArticleModule } from "@/features/article/ArticleModule";
import { UserModule } from "@/features/user/UserModule";
import { Article } from "@/features/article/data/Article";
import { User } from "@/features/user/data/User";

// Module factory helper
const moduleFactory = (params: {
  pageUrl?: string;
  name: string;
  cache?: string;
  model: any;
  feature?: string;
  moduleId?: string;
  inclusions?: Record<string, { types?: string[]; fields?: FieldSelector<any>[] }>;
}): ModuleWithPermissions => ({
  pageUrl: params.pageUrl,
  name: params.name,
  model: params.model,
  feature: params.feature,
  moduleId: params.moduleId,
  cache: params.cache,
  inclusions: params.inclusions ?? {},
});

// Example module definition file (e.g., ArticleModule.ts)
// export const ArticleModule = (factory: ModuleFactory) =>
//   factory({ name: "articles", model: Article, pageUrl: "/articles" });

// Single source of truth for all modules
const allModules = {
  Article: ArticleModule(moduleFactory),
  User: UserModule(moduleFactory),
  S3: S3Module(moduleFactory), // Built-in S3 module from library
} satisfies Record<string, ModuleWithPermissions>;

// Export type for TypeScript autocompletion
export type AllModuleDefinitions = typeof allModules;

let bootstrapped = false;

export function bootstrap(): void {
  if (bootstrapped) return;

  // Register modules for Modules.X access
  Object.entries(allModules).forEach(([name, module]) => {
    ModuleRegistry.register(name, module);
  });

  // Register model classes for JSON:API response translation
  DataClassRegistry.bootstrap(allModules);

  bootstrapped = true;
}

3. Fetch Data in Server Components

// src/app/articles/page.tsx
import { JsonApiGet } from "@carlonicora/nextjs-jsonapi";
import { Modules } from "@carlonicora/nextjs-jsonapi/core";

export default async function ArticlesPage() {
  const response = await JsonApiGet({
    classKey: Modules.Article,
    endpoint: "/articles",
    language: "en",
  });

  if (!response.ok) {
    return <div>Error: {response.error}</div>;
  }

  return (
    <ul>
      {response.data.map((article) => (
        <li key={article.id}>{article.title}</li>
      ))}
    </ul>
  );
}

4. Use Hooks in Client Components

"use client";

import { useJsonApiGet, useJsonApiMutation } from "@carlonicora/nextjs-jsonapi/client";
import { Modules } from "@carlonicora/nextjs-jsonapi/core";

export function ArticleList() {
  const { data, loading, error, refetch } = useJsonApiGet({
    classKey: Modules.Article,
    endpoint: "/articles",
  });

  const { mutate, loading: creating } = useJsonApiMutation({
    method: "POST",
    classKey: Modules.Article,
    onSuccess: () => refetch(),
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <button
        onClick={() => mutate({
          endpoint: "/articles",
          body: { title: "New Article" }
        })}
        disabled={creating}
      >
        Create Article
      </button>
      <ul>
        {data.map((article) => (
          <li key={article.id}>{article.title}</li>
        ))}
      </ul>
    </div>
  );
}

Environment Variables

# Required
NEXT_PUBLIC_API_URL=http://localhost:3000

# Optional - Token cookie name (default: "token")
# Set this if your API uses a different cookie name for JWT tokens

Entry Points

Main Export (.)

The default export provides the unified API that auto-detects the environment:

import {
  JsonApiGet,
  JsonApiPost,
  JsonApiPut,
  JsonApiPatch,
  JsonApiDelete,
  configureJsonApi,
} from "@carlonicora/nextjs-jsonapi";

Core (/core)

Core interfaces, factories, registries, and utilities:

import {
  // Interfaces
  ApiDataInterface,
  ApiRequestDataTypeInterface,
  ApiResponseInterface,

  // Factories
  JsonApiDataFactory,

  // Registries
  ModuleRegistry, // Register modules during bootstrap
  DataClassRegistry, // Register model classes for JSON:API translation
  Modules, // Access registered modules (e.g., Modules.Article)

  // Endpoint builder
  EndpointBuilder,

  // Field selectors
  FieldSelector,

  // Utilities
  translateResponse,
} from "@carlonicora/nextjs-jsonapi/core";

Client (/client)

React hooks and client-side utilities (requires "use client"):

import {
  // Hooks
  useJsonApiGet,
  useJsonApiMutation,
  useRehydration,

  // Context
  JsonApiProvider,
  useJsonApiContext,

  // Request utilities
  directFetch,
  getClientToken,
} from "@carlonicora/nextjs-jsonapi/client";

Server (/server)

Server-side request utilities:

import { serverRequest, getServerToken, getCacheProfile } from "@carlonicora/nextjs-jsonapi/server";

Permissions (/permissions)

Permission checking utilities:

import {
  checkPermission,
  type PermissionCheck,
  type ModuleWithPermissions,
  type ModuleFactory,
} from "@carlonicora/nextjs-jsonapi/permissions";

Features (/features)

Built-in feature modules that can be used directly in your application:

import {
  // S3 Module (for file uploads via pre-signed URLs)
  S3Module, // Module definition factory
  S3Service, // Service with getPreSignedUrl, getSignedUrl, deleteFile
  S3, // Data class
  type S3Interface, // Response interface
  type S3Input, // Input parameters
} from "@carlonicora/nextjs-jsonapi/features";

// Usage example:
const s3Response = await S3Service.getPreSignedUrl({
  key: "companies/123/documents/file.pdf",
  contentType: "application/pdf",
  isPublic: true,
});

await fetch(s3Response.url, {
  method: "PUT",
  headers: s3Response.headers,
  body: file,
});

Utils (/utils)

Utility functions:

import {
  cn, // Class name merger (clsx + tailwind-merge)
  composeRefs, // Compose multiple refs
  useComposedRefs, // Hook for composing refs
  useIsMobile, // Mobile detection hook
  type ClassValue, // Type for cn function
} from "@carlonicora/nextjs-jsonapi/utils";

shadcn/ui (/shadcnui)

All shadcn/ui components (requires "use client"):

import {
  // UI Components (41)
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
  Alert,
  AlertTitle,
  AlertDescription,
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent /* ... */,
  Avatar,
  AvatarImage,
  AvatarFallback,
  Badge,
  badgeVariants,
  Breadcrumb,
  BreadcrumbList,
  BreadcrumbItem /* ... */,
  Button,
  buttonVariants,
  Calendar,
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselPrevious,
  CarouselNext,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  ChartLegend /* ... */,
  Checkbox,
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
  Command,
  CommandInput,
  CommandList,
  CommandItem /* ... */,
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent /* ... */,
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader /* ... */,
  Drawer,
  DrawerTrigger,
  DrawerContent /* ... */,
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent /* ... */,
  Form,
  FormField,
  FormItem,
  FormLabel,
  FormControl /* ... */,
  HoverCard,
  HoverCardTrigger,
  HoverCardContent,
  Input,
  Label,
  NavigationMenu,
  NavigationMenuList,
  NavigationMenuItem /* ... */,
  Popover,
  PopoverTrigger,
  PopoverContent,
  Progress,
  RadioGroup,
  RadioGroupItem,
  ScrollArea,
  ScrollBar,
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem /* ... */,
  Separator,
  Sheet,
  SheetTrigger,
  SheetContent,
  SheetHeader /* ... */,
  Sidebar,
  SidebarProvider,
  SidebarContent,
  SidebarMenu /* ... */,
  Skeleton,
  Slider,
  Sonner,
  Toaster,
  toast,
  Switch,
  Table,
  TableHeader,
  TableBody,
  TableRow,
  TableHead,
  TableCell /* ... */,
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
  Textarea,
  Toggle,
  toggleVariants,
  Tooltip,
  TooltipTrigger,
  TooltipContent,
  TooltipProvider,

  // Custom Components (3)
  Kanban,
  KanbanColumn,
  KanbanItem, // Drag-and-drop Kanban board
  Link, // next-intl compatible Link
  MultiSelect, // Multi-select dropdown
} from "@carlonicora/nextjs-jsonapi/shadcnui";

Unified API

The unified API automatically detects whether code is running on the server or client and uses the appropriate request method.

JsonApiGet

Fetch data from a JSON:API endpoint:

const response = await JsonApiGet({
  classKey: Modules.Article, // Module definition
  endpoint: "/articles/123", // API endpoint
  companyId: "company-uuid", // Optional: for multi-tenant apps
  language: "en", // Required: for i18n
});

if (response.ok) {
  console.log(response.data); // Deserialized data
  console.log(response.pagination); // Pagination info

  // Navigate pages
  if (response.next) {
    const nextPage = await response.nextPage();
  }
}

JsonApiPost

Create a new resource:

const response = await JsonApiPost({
  classKey: Modules.Article,
  endpoint: "/articles",
  body: { title: "New Article", content: "..." },
  language: "en",

  // Optional
  files: { attachment: file }, // File uploads
  overridesJsonApiCreation: false, // Use raw body instead of JSON:API format
  responseType: Modules.OtherType, // If response type differs
});

JsonApiPut / JsonApiPatch

Update a resource:

const response = await JsonApiPut({
  classKey: Modules.Article,
  endpoint: "/articles/123",
  body: { title: "Updated Title" },
  language: "en",
});

JsonApiDelete

Delete a resource:

const response = await JsonApiDelete({
  classKey: Modules.Article,
  endpoint: "/articles/123",
  language: "en",
});

Client Hooks

useJsonApiGet

Hook for fetching data with automatic refetching:

const {
  data, // Fetched data or null
  loading, // Loading state
  error, // Error message or null
  response, // Full API response
  refetch, // Manual refetch function
  hasNextPage, // Pagination: has next page
  hasPreviousPage, // Pagination: has previous page
  fetchNextPage, // Fetch next page
  fetchPreviousPage, // Fetch previous page
} = useJsonApiGet<Article>({
  classKey: Modules.Article,
  endpoint: `/articles/${id}`,
  companyId: companyId,
  options: {
    enabled: !!id, // Conditionally enable
    deps: [someDependency], // Refetch when these change
  },
});

useJsonApiMutation

Hook for mutations (POST, PUT, PATCH, DELETE):

const {
  data, // Result data or null
  loading, // Loading state
  error, // Error message or null
  response, // Full API response
  mutate, // Execute the mutation
  reset, // Reset state
} = useJsonApiMutation<Article>({
  method: "POST",
  classKey: Modules.Article,
  onSuccess: (data) => console.log("Created:", data),
  onError: (error) => console.error("Failed:", error),
});

// Execute mutation
const result = await mutate({
  endpoint: "/articles",
  body: { title: "New Article" },
  files: { image: imageFile },
  companyId: "company-uuid",
});

Server Requests

For server components or API routes, use the server module directly:

import { serverRequest, getServerToken, getCacheProfile } from "@carlonicora/nextjs-jsonapi/server";

export async function getArticle(id: string) {
  const token = await getServerToken();

  const data = await serverRequest({
    method: "GET",
    url: `${process.env.NEXT_PUBLIC_API_URL}/articles/${id}`,
    token,
    cache: getCacheProfile("articles"), // Get cache settings
    language: "en",
  });

  return data;
}

Cache Profiles

The library supports Next.js 16+ caching via cacheLife() and cacheTag():

// In your module definition
export const Modules = {
  Article: {
    type: "articles",
    cache: "articles", // Profile name for caching
    factory: (data: any) => data,
  },
};

// The cache profile is automatically applied when using JsonApiGet
// on the server side

Permissions

Check user permissions for protected resources:

import { checkPermission } from "@carlonicora/nextjs-jsonapi/permissions";

// Check if user has permission
const canEdit = checkPermission({
  user: currentUser,
  action: "edit",
  resource: "articles",
  resourceId: article.id,
});

if (!canEdit) {
  return <div>Access denied</div>;
}

shadcn/ui Components

The package includes 44 pre-built shadcn/ui components:

Standard UI Components (41)

| Component | Description | | ---------------- | ------------------------------- | | Accordion | Collapsible content sections | | Alert | Callout for important messages | | AlertDialog | Modal dialog for confirmations | | Avatar | User profile images | | Badge | Status indicators and labels | | Breadcrumb | Navigation breadcrumbs | | Button | Click actions with variants | | Calendar | Date picker calendar | | Card | Content container | | Carousel | Sliding content panels | | Chart | Data visualization (Recharts) | | Checkbox | Toggle options | | Collapsible | Expandable sections | | Command | Command palette (cmdk) | | ContextMenu | Right-click menus | | Dialog | Modal windows | | Drawer | Sliding side panels (Vaul) | | DropdownMenu | Dropdown menus | | Form | Form handling (react-hook-form) | | HoverCard | Hover-triggered cards | | Input | Text input fields | | Label | Form labels | | NavigationMenu | Navigation menus | | Popover | Floating content | | Progress | Progress indicators | | RadioGroup | Radio button groups | | ScrollArea | Custom scrollbars | | Select | Dropdown selects | | Separator | Visual dividers | | Sheet | Side panels | | Sidebar | Application sidebars | | Skeleton | Loading placeholders | | Slider | Range sliders | | Sonner | Toast notifications | | Switch | Toggle switches | | Table | Data tables | | Tabs | Tabbed interfaces | | Textarea | Multi-line text input | | Toggle | Toggle buttons | | Tooltip | Hover tooltips |

Custom Components (3)

| Component | Description | | ------------- | ------------------------------------ | | Kanban | Drag-and-drop Kanban board (dnd-kit) | | Link | next-intl compatible link wrapper | | MultiSelect | Multi-select dropdown with badges |

Usage Example

"use client";

import {
  Button,
  Card,
  CardHeader,
  CardTitle,
  CardContent,
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@carlonicora/nextjs-jsonapi/shadcnui";
import { cn } from "@carlonicora/nextjs-jsonapi/utils";

export function ArticleCard({ article, className }) {
  return (
    <Card className={cn("hover:shadow-lg transition-shadow", className)}>
      <CardHeader>
        <CardTitle>{article.title}</CardTitle>
      </CardHeader>
      <CardContent>
        <p>{article.excerpt}</p>

        <Dialog>
          <DialogTrigger asChild>
            <Button variant="outline">Read More</Button>
          </DialogTrigger>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>{article.title}</DialogTitle>
            </DialogHeader>
            <p>{article.content}</p>
          </DialogContent>
        </Dialog>
      </CardContent>
    </Card>
  );
}

Tailwind CSS Configuration

Important for Tailwind v4: You must add the @source directive to your globals.css to ensure Tailwind scans the package's component files:

/* apps/web/src/app/globals.css */
@import "tailwindcss";
@import "tw-animate-css";

/* Include package source files for Tailwind to scan */
@source "../../../../packages/nextjs-jsonapi/src/**/*.{ts,tsx}";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  /* ... other theme variables */
}

The @source path should be relative from your globals.css to the package's src directory.

CSS Variables

The shadcn/ui components require CSS variables to be defined in your application. Add these to your globals.css:

:root {
  /* Background & Foreground */
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);

  /* Primary */
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);

  /* Secondary */
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);

  /* Muted */
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);

  /* Accent */
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);

  /* Destructive */
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);

  /* Border & Input */
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);

  /* Card & Popover */
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);

  /* Charts */
  --chart-1: oklch(0.646 0.222 41.116);
  --chart-2: oklch(0.6 0.118 184.704);
  --chart-3: oklch(0.398 0.07 227.392);
  --chart-4: oklch(0.828 0.189 84.429);
  --chart-5: oklch(0.769 0.188 70.08);

  /* Sidebar */
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.145 0 0);
  --sidebar-primary: oklch(0.205 0 0);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.97 0 0);
  --sidebar-accent-foreground: oklch(0.205 0 0);
  --sidebar-border: oklch(0.922 0 0);
  --sidebar-ring: oklch(0.708 0 0);

  /* Warning */
  --warning: oklch(0.84 0.16 84);
  --warning-foreground: oklch(0.28 0.07 46);

  /* Radius */
  --radius: 0.625rem;
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.269 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
  /* ... other dark mode values */
}

License

This project is licensed under GPL v3 for open source use.

For commercial/closed-source licensing, contact: @carlonicora

Author

Carlo Nicora - @carlonicora