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

@django-next/client

v2.2.0

Published

React client package for Django-Next with authentication, RBAC, file uploads, and seamless integration with generated SDKs

Readme

@django-next/client

npm version TypeScript

React client package for Django-Next with authentication, RBAC, file uploads, and seamless integration with generated SDKs.

🌟 What is this package?

The @django-next/client package provides React components, hooks, and utilities that work seamlessly with the SDK generated by @django-next/cli. It handles:

  • Authentication - Login, logout, and session management
  • Protected Routes - Role-based access control (RBAC)
  • File Uploads - Progress tracking and error handling
  • API Context - Seamless integration with generated SDKs
  • Error Handling - Comprehensive error boundaries

This package works hand-in-hand with the generated SDK!

📦 Installation

# Install the client package
npm install @django-next/client

# Install required peer dependencies
npm install @tanstack/react-query axios zod

# Don't forget the CLI for generating your SDK
npm install --save-dev @django-next/cli

🚀 Features

  • 🔐 Authentication - Complete login/logout with session management
  • 🛡️ Protected Routes - Role-based access control (RBAC) components
  • 📁 File Uploads - Progress tracking and error handling
  • 🔗 API Integration - Seamless connection with generated SDKs
  • ⚡ React Query - Enhanced query provider and utilities
  • 📝 TypeScript - Full type safety throughout
  • 🎯 Beginner Friendly - Easy to use with clear examples

🏃‍♂️ Quick Start

Step 1: Generate your SDK first

# Install and use the CLI to generate your SDK
npm install -g @django-next/cli
django-next init
django-next generate

Step 2: Set up providers

// app/providers.tsx
'use client';

import { DjangoNextProvider } from '@django-next/client';
import { ApiClient } from '../.django-next/api'; // Your generated API client

const apiClient = new ApiClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
  withCredentials: true,
});

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <DjangoNextProvider
      apiClient={apiClient}
      authConfig={{
        loginUrl: '/api/auth/login/',
        logoutUrl: '/api/auth/logout/',
        userUrl: '/api/auth/me/',
        refreshUrl: '/api/auth/refresh/',
      }}
    >
      {children}
    </DjangoNextProvider>
  );
}

Step 3: Use in your layout

// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  );
}

Step 3: Use Generated Hooks

No additional setup needed! The hooks work automatically with DjangoNextProvider.

📖 Usage Examples

Authentication

// components/LoginForm.tsx
import { useAuth } from '@django-next/client';
import { useApi_auth_login_create } from '../.django-next/hooks'; // Generated hook

export function LoginForm() {
  const { isAuthenticated, user, logout } = useAuth();

  const loginMutation = useApi_auth_login_create({
    onSuccess: () => {
      console.log('Login successful!');
      // Auth state is automatically updated
    }
  });

  if (isAuthenticated) {
    return (
      <div>
        <p>Welcome, {user?.username}!</p>
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  const handleLogin = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);

    loginMutation.mutate({
      username: formData.get('username') as string,
      password: formData.get('password') as string,
    });
  };

  return (
    <form onSubmit={handleLogin}>
      <input name="username" placeholder="Username" required />
      <input name="password" type="password" placeholder="Password" required />
      <button type="submit" disabled={loginMutation.isPending}>
        {loginMutation.isPending ? 'Logging in...' : 'Login'}
      </button>
      {loginMutation.error && (
        <p style={{ color: 'red' }}>Error: {loginMutation.error.message}</p>
      )}
    </form>
  );
}

Protected Routes & RBAC

// components/AdminPanel.tsx
import { Protected } from '@django-next/client';
import { useApi_users_list } from '../lib/api/hooks'; // Generated hook

export function AdminPanel() {
  return (
    <Protected
      hasAll={["users.view_user"]}
      fallback={<div>Access denied. Admin permissions required.</div>}
    >
      <AdminContent />
    </Protected>
  );
}

function AdminContent() {
  const { data: users, isLoading } = useUsersList();

  return (
    <div>
      <h1>Admin Panel</h1>

      {/* Only show edit button if user has permission */}
      <Protected hasAll={["users.change_user"]}>
        <button>Edit Users</button>
      </Protected>

      {/* Only show for staff members */}
      <RequireStaff>
        <button>Staff Only Feature</button>
      </RequireStaff>

      {/* Multiple permissions required */}
      <Protected hasAll={["users.add_user", "users.delete_user"]}>
        <button>Advanced User Management</button>
      </Protected>
    </div>
  );
}

File Uploads

// components/FileUpload.tsx
import { useApi_files_create } from '../lib/api/hooks'; // Generated hook
import { useState } from 'react';

export function FileUpload() {
  const [file, setFile] = useState<File | null>(null);
  const [progress, setProgress] = useState(0);

  const uploadMutation = useApi_files_create({
    onUploadProgress: (progressValue) => {
      setProgress(progressValue);
    },
    onSuccess: () => {
      setFile(null);
      setProgress(0);
      alert('File uploaded successfully!');
    }
  });

  const handleUpload = () => {
    if (!file) return;

    uploadMutation.mutate({
      file,
      title: file.name,
      description: 'Uploaded via Django-Next'
    });
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => setFile(e.target.files?.[0] || null)}
      />

      {file && (
        <div>
          <p>Selected: {file.name}</p>
          <button onClick={handleUpload} disabled={uploadMutation.isPending}>
            {uploadMutation.isPending ? 'Uploading...' : 'Upload'}
          </button>

          {uploadMutation.isPending && (
            <div>
              <div>Progress: {progress}%</div>
              <progress value={progress} max={100} />
            </div>
          )}
        </div>
      )}
    </div>
  );
}

🔧 API Reference

DjangoNextProvider

Provides unified API client, authentication state, and React Query configuration throughout your app.

import { useAuth } from '@django-next/client';

const {
  isAuthenticated,  // boolean
  user,            // User object or null
  isLoading,       // boolean
  login,           // (credentials) => Promise
  logout,          // () => Promise
  checkPermission, // (permission) => boolean
  hasRole,         // (role) => boolean
  error            // Error object or null
} = useAuth();

Protected Component

Conditionally render content based on authentication and permissions.

<Protected
  hasAll={["posts.view_post"]}          // User must have ALL specified permissions
  hasAnyRole={["admin", "moderator"]}   // User must have AT LEAST ONE of the specified roles
  requireAllPermissions={true}          // Require ALL permissions (default: any)
  fallback={<div>Access denied</div>}   // What to show when access denied
  onAccessDenied={() => console.log('Access denied')} // Callback
>
  <YourProtectedContent />
</Protected>

Backend Security Checklist (for Django with Simple JWT)

To ensure secure authentication and session management, configure your Django backend as follows:

  • JWT in httpOnly Cookies: Issue JWT access and refresh tokens only in httpOnly, Secure, SameSite=Strict cookies. Do not expose tokens in localStorage or headers.
  • CSRF Protection: Enable Django's CSRF middleware. Rotate CSRF tokens on login/logout and ensure the client updates the token.
  • Token Expiry Handling: On refresh token expiry or invalidation, return a clear error so the client can log out the user.
  • Secure Cookie Flags: Always set cookies with Secure, HttpOnly, and SameSite=Strict in production.
  • No Sensitive Data in LocalStorage: Never store sensitive tokens or user info in localStorage/sessionStorage.
  • CORS: Configure CORS to only allow trusted origins and support credentials.
  • Session Logout: Invalidate refresh tokens on logout and clear cookies on both client and server.

Django JWT HTTP-Only Cookie Setup

Since Simple JWT doesn't support HTTP-only cookies by default, you need to create custom views and authentication. Here's the complete setup:

1. Install Required Packages

pip install djangorestframework
pip install djangorestframework-simplejwt
pip install django-cors-headers  # if you need CORS

2. Django Settings Configuration

# settings.py
import os
from datetime import timedelta

IS_LOCAL = os.environ.get("DJANGO_ENV") == "local"

INSTALLED_APPS = [
    # ... your other apps
    'rest_framework',
    'rest_framework_simplejwt',
    'rest_framework_simplejwt.token_blacklist',  # For secure logout
    'corsheaders',  # if using CORS
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # if using CORS
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Django REST Framework settings
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'your_app.authentication.JWTCookieAuthentication',  # Your custom auth class
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# Simple JWT settings
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    'ROTATE_REFRESH_TOKENS': True,  # Generate new refresh token on refresh
    'BLACKLIST_AFTER_ROTATION': True,  # Blacklist old refresh tokens
}

# Custom JWT Cookie settings
JWT_COOKIE_NAME = 'access_token'
JWT_REFRESH_COOKIE_NAME = 'refresh_token'
JWT_COOKIE_SECURE = not IS_LOCAL  # Secure only in production
JWT_COOKIE_HTTP_ONLY = True
JWT_COOKIE_SAME_SITE = 'Strict'

# Standard Django cookie settings
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Strict"
SESSION_COOKIE_SECURE = not IS_LOCAL

CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = "Strict"
CSRF_COOKIE_SECURE = not IS_LOCAL

# CORS settings (if frontend and backend are on different ports/domains)
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",  # Frontend dev server
    "https://your-production-domain.com",
]

3. Create Custom Authentication Class

Create your_app/authentication.py:

# authentication.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.conf import settings

class JWTCookieAuthentication(JWTAuthentication):
    """
    Custom JWT authentication that reads tokens from HTTP-only cookies
    instead of Authorization headers
    """
    def authenticate(self, request):
        # Get token from cookie
        raw_token = request.COOKIES.get(settings.JWT_COOKIE_NAME)
        
        if raw_token is None:
            return None
        
        # Validate token using Simple JWT's built-in validation
        validated_token = self.get_validated_token(raw_token)
        user = self.get_user(validated_token)
        
        return (user, validated_token)

4. Create Custom Views for Cookie Management

Create your_app/views.py:

# views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from django.conf import settings

class CookieTokenObtainPairView(TokenObtainPairView):
    """
    Login view that sets JWT tokens in HTTP-only cookies
    """
    def post(self, request, *args, **kwargs):
        response = super().post(request, *args, **kwargs)
        
        if response.status_code == 200:
            access_token = response.data['access']
            refresh_token = response.data['refresh']
            
            # Remove tokens from response body for security
            response.data = {'message': 'Login successful'}
            
            # Set access token cookie
            response.set_cookie(
                settings.JWT_COOKIE_NAME,
                access_token,
                max_age=settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'].total_seconds(),
                httponly=settings.JWT_COOKIE_HTTP_ONLY,
                secure=settings.JWT_COOKIE_SECURE,
                samesite=settings.JWT_COOKIE_SAME_SITE,
            )
            
            # Set refresh token cookie
            response.set_cookie(
                settings.JWT_REFRESH_COOKIE_NAME,
                refresh_token,
                max_age=settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'].total_seconds(),
                httponly=settings.JWT_COOKIE_HTTP_ONLY,
                secure=settings.JWT_COOKIE_SECURE,
                samesite=settings.JWT_COOKIE_SAME_SITE,
            )
        
        return response

class CookieTokenRefreshView(TokenRefreshView):
    """
    Token refresh view that handles HTTP-only cookies
    """
    def post(self, request, *args, **kwargs):
        # Get refresh token from cookie
        refresh_token = request.COOKIES.get(settings.JWT_REFRESH_COOKIE_NAME)
        
        if not refresh_token:
            return Response(
                {'error': 'Refresh token not found'}, 
                status=status.HTTP_401_UNAUTHORIZED
            )
        
        # Add to request data for parent class processing
        request.data['refresh'] = refresh_token
        response = super().post(request, *args, **kwargs)
        
        if response.status_code == 200:
            access_token = response.data['access']
            response.data = {'message': 'Token refreshed'}
            
            # Set new access token cookie
            response.set_cookie(
                settings.JWT_COOKIE_NAME,
                access_token,
                max_age=settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'].total_seconds(),
                httponly=settings.JWT_COOKIE_HTTP_ONLY,
                secure=settings.JWT_COOKIE_SECURE,
                samesite=settings.JWT_COOKIE_SAME_SITE,
            )
        
        return response

@api_view(['POST'])
@permission_classes([AllowAny])
def logout_view(request):
    """
    Logout view that clears HTTP-only cookies and blacklists refresh token
    """
    refresh_token = request.COOKIES.get(settings.JWT_REFRESH_COOKIE_NAME)
    
    # Blacklist the refresh token for security
    if refresh_token:
        try:
            token = RefreshToken(refresh_token)
            token.blacklist()
        except Exception:
            pass  # Token might already be invalid
    
    response = Response({'message': 'Logged out successfully'})
    
    # Clear both cookies
    response.delete_cookie(settings.JWT_COOKIE_NAME)
    response.delete_cookie(settings.JWT_REFRESH_COOKIE_NAME)
    
    return response

5. Configure URLs

In your urls.py:

# urls.py
from django.urls import path
from .views import CookieTokenObtainPairView, CookieTokenRefreshView, logout_view

urlpatterns = [
    # Replace default Simple JWT URLs with cookie versions
    path('api/token/', CookieTokenObtainPairView.as_view(), name='login'),
    path('api/token/refresh/', CookieTokenRefreshView.as_view(), name='refresh'),
    path('api/token/logout/', logout_view, name='logout'),
    # ... your other URLs
]

Setup

Recommended Setup (Using createDjangoClient)

The recommended way to set up the client is using createDjangoClient() which ensures proper configuration:

// lib/api-client.ts
import { createDjangoClient } from '@django-next/client';
import { ApiClient } from './api/api'; // Your generated API client

export const { api, axiosInstance, config } = createDjangoClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
  apiClass: ApiClient,
  auth: {
    loginUrl: '/api/auth/login/',
    logoutUrl: '/api/auth/logout/',
    userUrl: '/api/auth/me/',
    refreshUrl: '/api/auth/refresh/',
  },
  timeout: 30000,
  withCredentials: true,
  onError: (error) => console.error('API Error:', error),
});

Alternative Setup (Direct API Client)

If you prefer to use the API client directly, make sure to provide a baseURL:

// lib/api-client.ts
import { ApiClient } from './api/api';

export const api = new ApiClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
  timeout: 30000,
  withCredentials: true,
  auth: {
    loginUrl: '/api/auth/login/',
    logoutUrl: '/api/auth/logout/',
    userUrl: '/api/auth/me/',
    refreshUrl: '/api/auth/refresh/',
  },
});

⚠️ Important: Always provide a baseURL when creating the API client directly, otherwise authentication and API calls may fail.

Quick Start

  1. Generate the SDK using the CLI:
    pnpm dlx @django-next/cli generate
  2. Set up the client using one of the methods above.
  3. Import and use the generated files in your Next.js app.

Usage Examples

API Client

import { ApiClient } from './.django-next/api';
const api = new ApiClient();
const data = await api.someEndpoint(params);

React Query Hooks

import { useSomeEndpoint } from './.django-next/hooks';
const { data, isLoading, error, isError } = useSomeEndpoint(params);
if (isError) {
  // Handle or display error.message
}

DjangoNextProvider & useAuth

import { DjangoNextProvider, useAuth } from '@django-next/client';
<DjangoNextProvider apiClient={apiClient}>
  <YourApp />
</DjangoNextProvider>

Protected (RBAC)

import { Protected } from '@django-next/client';
<Protected hasAll={['admin']} fallback={<div>Access denied</div>}>
  <AdminPanel />
</Protected>

File Upload

import { uploadFile } from '@django-next/client/upload-file';
await uploadFile(api.axios, '/api/upload/', file, { extraField: 'value' }, {
  onProgress: (percent) => console.log(`Upload: ${percent}%`)
});

Batch API Calls

import { batchApiCalls } from '@django-next/client/batch-api-calls';
// Atom mode (default): fail on first error
try {
  const results = await batchApiCalls([
    () => api.getUser({ id: 1 }),
    () => api.getPosts({ page: 1 }),
  ]);
} catch (err) {
  // Handle/log error, inspect which call failed
}
// Non-atom mode: get all results/errors
const results = await batchApiCalls([
  () => api.getUser({ id: 1 }),
  () => api.getPosts({ page: 1 }),
], { atom: false });
results.forEach((res, i) => {
  if ('error' in res) {
    // Handle error for call i
  } else {
    // Use result for call i
  }
});

Batch Query Hook

import { useBatchQuery } from '@django-next/client/use-batch-query';
const { data, isLoading, error } = useBatchQuery([
  { queryKey: ['user'], queryFn: () => api.getUser({ id: 1 }) },
  { queryKey: ['posts'], queryFn: () => api.getPosts({ page: 1 }) },
]);
if (error) {
  // Show error message in UI
}

Server Actions (Next.js)

import { someEndpointAction } from './.django-next/actions';
export async function action(formData) {
  try {
    return await someEndpointAction(formData);
  } catch (err) {
    // Handle/log error, return custom error response, etc.
    return { error: err instanceof Error ? err.message : String(err) };
  }
}

Error Handling

  • Hooks: Use error/isError from React Query hooks to display or handle errors in your UI.
  • Batch:
    • With atom: true (default): use try/catch to handle the first error.
    • With atom: false: inspect each result; errors are returned as { error: string } objects.
  • Server Actions: Always wrap generated actions in try/catch to handle errors gracefully. Actions return { error: string } on failure.

Troubleshooting

Common Configuration Issues

Authentication requests go to wrong URL (e.g., /auth instead of http://localhost:8000/api/auth/login/)

Problem: Login requests are being sent to relative paths without the base URL.

Solution: Ensure you're providing a baseURL when creating the API client:

// ❌ Wrong - missing baseURL
const api = new ApiClient();

// ✅ Correct - with baseURL
const api = new ApiClient({
  baseURL: 'http://localhost:8000',
});

// ✅ Better - use createDjangoClient
const { api } = createDjangoClient({
  baseUrl: 'http://localhost:8000',
  apiClass: ApiClient,
});

DjangoNextProvider throws "axios instance not found" error

Problem: The API client doesn't have a properly configured axios instance.

Solution: Use createDjangoClient() or ensure your API client has the _config property set up correctly.

Configuration not being applied

Problem: Auth URLs or other configuration options are not being used.

Solution: Make sure you're passing the configuration to the right place:

// For createDjangoClient
const { api } = createDjangoClient({
  baseUrl: 'http://localhost:8000',
  apiClass: ApiClient,
  auth: {
    loginUrl: '/api/auth/login/',
    // ... other auth config
  },
});

// For direct API client usage
const api = new ApiClient({
  baseURL: 'http://localhost:8000',
  auth: {
    loginUrl: '/api/auth/login/',
    // ... other auth config
  },
});

Other Common Issues

  • Type errors: Re-run codegen to sync with your API schema.
  • Auth/session issues: Check your Django backend and config.
  • File upload issues: Ensure your endpoint accepts multipart/form-data.

For more, see the generated SDK docs in .django-next/ after running the CLI.