prisma-jotai-client

6 min read Original article โ†—

Prisma Jotai Client ๐Ÿš€

npm version TypeScript License: MIT Downloads

A fully dynamic Prisma client wrapper for Jotai with conditional typing and built-in pagination support

Installation โ€ข Quick Start โ€ข API โ€ข Examples โ€ข Contributing

โœจ Features

  • ๐Ÿ”„ Fully Dynamic: All models and operations extracted automatically from generated Prisma types
  • ๐ŸŽฏ Conditional Typing: Return types inferred based on parameters (select, include, omit, paginated)
  • ๐Ÿ“„ Built-in Pagination: Native pagination support with automatic metadata
  • ๐Ÿ›ก๏ธ Type Safe: Complete TypeScript support with full autocompletion
  • โšก Optimized: Intelligent caching with atom families and isEqual
  • ๐ŸŽช Zero Config: No manual configuration required
  • ๐Ÿ”ง Extensible: New models automatically supported
  • ๐ŸŒ Universal: Works with any Prisma schema

๐Ÿ—๏ธ Architecture

This library creates a bridge between Prisma and Jotai by:

  • Using dynamic proxies to intercept model access
  • Leveraging generated Prisma types (Prisma.TypeMap, Prisma.ModelName)
  • Creating cached atom families for optimal performance
  • Providing advanced TypeScript inference for conditional return types

๐Ÿ“ฆ Installation

npm install prisma-jotai-client

# or
yarn add prisma-jotai-client

# or
pnpm add prisma-jotai-client

# or
bun add prisma-jotai-client

Peer Dependencies

Make sure you have the required peer dependencies installed:

npm install @prisma/client jotai react

๐Ÿš€ Quick Start

  1. Generate your Prisma client (if not already done):
  1. Import and use:
import { $ } from 'prisma-jotai-client';
import { useAtom } from 'jotai';

function UserProfile({ userId }: { userId: string }) {
  // Automatically typed based on your Prisma schema
  const [userAtom] = useAtom($.user.findUnique({
    where: { id: userId },
    include: { posts: true, profile: true }
  }));

  // userAtom type: Atom<Promise<User & { posts: Post[]; profile: Profile | null } | null>>
  
  return (
    <Suspense fallback="Loading...">
      <UserDisplay userAtom={userAtom} />
    </Suspense>
  );
}

๐ŸŽฏ Conditional Typing

The return type changes automatically based on the parameters you use:

// SELECT: Returns only selected fields
const userWithSelect = $.user.findUnique({
  where: { id: 'user-123' },
  select: { id: true, email: true }
});
// Type: Atom<Promise<{ id: string; email: string } | null>>

// INCLUDE: Returns full object + relations
const userWithInclude = $.user.findUnique({
  where: { id: 'user-123' },
  include: { posts: true, comments: true }
});
// Type: Atom<Promise<User & { posts: Post[]; comments: Comment[] } | null>>

// Default: Returns base object
const userBasic = $.user.findUnique({
  where: { id: 'user-123' }
});
// Type: Atom<Promise<User | null>>

๐Ÿ“„ Pagination

Built-in pagination support with automatic metadata:

// Basic pagination
const paginatedUsers = $.user.findMany({
  take: 10,
  skip: 0,
  paginated: true
});
// Returns: { data: User[], total: number, take: number, skip: number }

// Pagination with filters and sorting
const filteredPosts = $.post.findMany({
  where: { published: true },
  orderBy: { createdAt: 'desc' },
  take: 25,
  skip: 50,
  paginated: true
});

// Pagination with SELECT
const paginatedWithSelect = $.user.findMany({
  select: { id: true, email: true },
  take: 20,
  paginated: true
});
// Type: { data: { id: string; email: string }[], total: number, take: number, skip: number }

Automatic metadata:

  • data: Query results
  • total: Total number of records
  • take: Applied limit
  • skip: Applied offset

๐Ÿ“š API Reference

Available Operations

All standard Prisma operations are supported automatically:

  • findUnique / findUniqueOrThrow
  • findFirst / findFirstOrThrow
  • findMany
  • create / createMany / createManyAndReturn
  • update / updateMany / updateManyAndReturn
  • delete / deleteMany
  • upsert
  • count
  • aggregate
  • groupBy

Dynamic Model Access

Access any model from your Prisma schema:

$.user.findMany()        // User operations
$.post.findMany()        // Post operations
$.comment.findMany()     // Comment operations
$.profile.findMany()     // Profile operations
// ... any model from your schema

Type-Safe Parameters

All parameters are fully typed based on your Prisma schema:

// TypeScript will provide autocompletion and validation
$.post.findMany({
  where: {
    title: { contains: 'prisma' },
    author: { email: { endsWith: '@example.com' } }
  },
  orderBy: { createdAt: 'desc' },
  include: { author: true, comments: true }
});

๐Ÿ’ก Examples

Real-World Usage Patterns

User Management with Search

function useUserList(search: string, page: number) {
  return useAtom($.user.findMany({
    where: search ? {
      OR: [
        { email: { contains: search, mode: 'insensitive' } },
        { name: { contains: search, mode: 'insensitive' } }
      ]
    } : undefined,
    orderBy: { createdAt: 'desc' },
    take: 20,
    skip: (page - 1) * 20,
    select: {
      id: true,
      email: true,
      name: true,
      createdAt: true
    },
    paginated: true
  }));
}

Blog Posts with Analytics

function useBlogPosts(authorId?: string) {
  return useAtom($.post.findMany({
    where: authorId ? { authorId } : { published: true },
    include: {
      author: { select: { name: true, email: true } },
      _count: { comments: true }
    },
    orderBy: { createdAt: 'desc' }
  }));
}

Advanced Filtering

function usePostsWithFilters(filters: {
  authorId?: string;
  dateFrom?: Date;
  dateTo?: Date;
  hasComments?: boolean;
}) {
  return useAtom($.post.findMany({
    where: {
      ...(filters.authorId && { authorId: filters.authorId }),
      ...(filters.dateFrom && { createdAt: { gte: filters.dateFrom } }),
      ...(filters.dateTo && { createdAt: { lte: filters.dateTo } }),
      ...(filters.hasComments !== undefined && {
        comments: filters.hasComments ? { some: {} } : { none: {} }
      })
    },
    include: { author: true, comments: true }
  }));
}

๐Ÿงช Testing

Run the included examples:

# See full demonstration
npm run demo

# Test pagination examples
npm run demo:pagination

๐Ÿ—๏ธ Development

Setup

# Clone the repository
git clone https://github.com/yourusername/prisma-jotai-client.git
cd prisma-jotai-client

# Install dependencies
npm install

# Run development build
npm run dev

Build

# Build for production
npm run build

# Type check
npm run type-check

# Lint
npm run lint

๐ŸŒŸ Benefits

  1. Conditional Typing: Return types inferred based on select/include/omit/paginated
  2. Native Pagination: Automatic metadata (total, take, skip)
  3. Zero Maintenance: New models automatically supported
  4. Type Safety: TypeScript errors for invalid models/fields
  5. Performance: Intelligent atom caching
  6. Developer Experience: Full IDE autocompletion
  7. Flexibility: Complete Prisma feature support

๐Ÿ”ง Configuration

TypeScript

Ensure your tsconfig.json includes:

{
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

Prisma

Works with any Prisma schema. Just run prisma generate and you're ready to go!

๐Ÿค Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Workflow

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Run npm run build and npm run type-check
  6. Submit a pull request

๐Ÿ“„ License

MIT ยฉ Your Name

๐Ÿ™ Acknowledgments

  • Prisma for the amazing ORM and type generation
  • Jotai for the excellent atomic state management
  • The React and TypeScript communities

๐Ÿ“ž Support