Skip to content

Shared Packages

Monorepo packages for code sharing across applications.

Current Packages

packages/
├── api-client/         # Shared HTTP client for frontend apps
├── ui/                 # Shared UI components (shadcn/ui)
├── eslint-config/      # ESLint configurations
├── typescript-config/  # TypeScript configurations
├── server-types/       # Server type exports
├── shared-types/       # Shared TypeScript types
└── vitest-config/      # Vitest test configurations

Package Details

@jubiloop/api-client

Centralized API client package that eliminates code duplication across frontend applications by providing a unified interface for API communication, authentication, and server state management.

Features

  • Core Client (createApiClient): Axios-based HTTP client with automatic CSRF protection
  • Package Factory (createApiPackage): Complete API setup requiring only baseURL configuration
  • Better Auth Integration: Authentication client with organization plugin support
  • Session Support: Cookie-based authentication with withCredentials enabled by default
  • Error Handling: Configurable 401 response handling for auth invalidation
  • TypeScript Support: Full type safety with interfaces for all configurations
  • TanStack Query Hooks: Pre-configured hooks for auth, organizations, health, and newsletter
  • Centralized Query Keys: Static query keys in defaultQueryKeys for consistent cache management

Architecture Benefits

  1. Consistency: All frontend apps use the same underlying HTTP client
  2. Flexibility: Each app can customize behavior while maintaining shared core
  3. Maintainability: Single source of truth for API communication logic
  4. Extensibility: Easy to add new features that benefit all apps

Usage Examples

typescript
// apps/webapp/src/lib/api/setup.ts
import { createApiPackage } from '@jubiloop/api-client'
import { queryKeys } from '@/integrations/tanstack-query/keys' // Re-exported from @jubiloop/api-client

export const api = createApiPackage({
  baseURL: import.meta.env.VITE_API_URL || 'https://api.jubiloop.localhost',
  onUnauthorized: () => {
    // Invalidate auth queries on 401
    queryClient.invalidateQueries({ queryKey: queryKeys.auth.all })
  },
})

// apps/marketing/src/lib/api/setup.ts
import { createApiPackage } from '@jubiloop/api-client'

export const api = createApiPackage({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'https://api.jubiloop.localhost',
  // No auth handling needed for marketing site
})

// Using the hooks in components
import { api } from '@/lib/api/setup'

function MyComponent() {
  const { data: health } = api.hooks.health.useHealthCheck()
  const { subscribe } = api.hooks.newsletter.useNewsletter()
  const { user, isAuthenticated } = api.hooks.auth.useAuth()
}

Package Structure

packages/api-client/
├── src/
│   ├── auth/              # Better Auth integration
│   │   ├── client.ts      # Auth client setup
│   │   └── types.ts       # Auth type definitions
│   ├── core/              # Core client implementation
│   │   └── client.ts      # Axios client factory
│   ├── hooks/             # React Query hooks
│   │   ├── auth.ts        # Authentication hooks
│   │   ├── health.ts      # Health check hooks
│   │   ├── newsletter.ts  # Newsletter subscription hooks
│   │   └── organization.ts # Organization management hooks
│   ├── queries/           # TanStack Query options
│   │   ├── health.ts      # Health query options
│   │   ├── organization.ts # Organization queries (moved from webapp)
│   │   └── session.ts     # Session queries (moved from webapp)
│   ├── keys/              # Query key management
│   │   └── defaults.ts    # Static defaultQueryKeys object
│   ├── routes/            # API endpoint definitions
│   │   └── index.ts       # Centralized route paths
│   └── index.ts           # Main package exports
├── tsup.config.ts         # Build configuration
└── package.json           # Dependencies

Migration from Duplicate Code

The api-client package consolidates previously duplicated code:

Before: Each app maintained its own:

  • API client setup
  • Auth queries and hooks
  • Health check implementations
  • Newsletter subscription logic

After: Single package provides everything:

  • Webapp imports from @jubiloop/api-client
  • Marketing imports from @jubiloop/api-client
  • Query keys centralized in defaultQueryKeys
  • Newsletter renamed from "subscription" for clarity

Build System

Uses tsup for bundling:

  • Multiple entry points for optimal tree-shaking
  • ESM and CommonJS outputs
  • TypeScript declarations
  • Separate builds for different import paths

@jubiloop/ui

Centralized UI component library using shadcn/ui. See UI Components for full documentation.

@jubiloop/eslint-config

Shared ESLint configurations:

  • base.js - Base JavaScript/TypeScript rules
  • next.js - Next.js-specific rules
  • react-internal.js - React rules
  • tanstack.js - TanStack-specific rules

@jubiloop/typescript-config

Shared TypeScript configurations:

  • base.json - Base configuration
  • nextjs.json - Next.js applications
  • react-library.json - React libraries

@jubiloop/server-types

Server-side TypeScript type definitions exported from the backend.

@jubiloop/shared-types

Centralized TypeScript type definitions shared between frontend and backend applications, eliminating duplication and ensuring type consistency across the stack.

Purpose

The shared-types package solves the problem of duplicate type definitions between backend and frontend by providing a single source of truth for all shared TypeScript interfaces and types.

Features

  • Entity Interfaces: Core domain models (e.g., IUser, IOrganization)
  • API Response Types: Standardized API response structures (IApiResponse, IApiMessage, IApiError)
  • DTOs: Data Transfer Objects for API communication (IUserMinimalDTO)
  • Request Types: API request interfaces (ISubscribeNewsletterRequest)
  • Health Check Types: System health monitoring interfaces (IHealthResponse, IDetailedHealthResponse)

Architecture

The package uses wildcard exports instead of barrel exports to:

  • Avoid circular dependency issues
  • Enable better tree-shaking
  • Provide cleaner import paths

Build System

The package employs a two-step build process:

Production Build (npm run build):

  • Uses tsup only
  • Generates JavaScript and type definitions
  • No declaration maps for security (prevents source exposure)

Development Build (npm run dev):

  • Uses tsup + tsc concurrently
  • Generates declaration maps (.d.ts.map files)
  • Enables IDE "Go to Definition" navigation to source TypeScript files

Package Structure

packages/shared-types/
├── src/
│   ├── entities/        # Domain entity interfaces
│   │   └── user.ts      # IUser interface
│   ├── dtos/            # Data Transfer Objects
│   │   └── user.ts      # User DTOs
│   ├── api/
│   │   ├── requests/    # API request types
│   │   │   └── newsletter.ts
│   │   └── responses/   # API response types
│   │       ├── common.ts     # IApiResponse, IApiError
│   │       └── health.ts     # Health check types
│   └── index.ts         # Re-exports (minimal)
├── tsup.config.ts       # Build configuration
└── package.json         # Package configuration

Usage Examples

typescript
// Import from specific paths (recommended)
import type { IUser } from '@jubiloop/shared-types/entities/user'
import type { IApiResponse } from '@jubiloop/shared-types/api/responses/common'
import type { IUserMinimalDTO } from '@jubiloop/shared-types/dtos/user'

// Backend: Type-safe API responses
const response: IApiResponse<IUser> = {
  success: true,
  data: user,
  message: { type: 'success', text: 'User fetched' },
}

// Frontend: Type-safe API consumption
const { data }: IApiResponse<IUser> = await api.get('/users/me')

TypeScript Naming Conventions

The package follows these conventions for clarity:

  • I prefix for interfaces: IUser, IApiClient, IHealthResponse
  • T prefix for type aliases: TSession, TAuthResponse (if needed)

This helps distinguish between interfaces and types at a glance.

@jubiloop/vitest-config

Test configuration for Vitest (currently unused as tests are not yet implemented).

Using Packages

Packages are linked using npm workspaces:

json
{
  "dependencies": {
    "@jubiloop/ui": "*",
    "@jubiloop/eslint-config": "*"
  }
}

Best Practices

  1. Keep packages focused on truly shared code
  2. Avoid app-specific logic in packages
  3. Use TypeScript for all package code
  4. Document public APIs with JSDoc comments

Built with ❤️ by the Jubiloop team