Shared API Package
What We Did
Section titled “What We Did”Created a new @repo/api package that centralizes all TanStack Query hooks for Convex API operations. This package provides type-safe useQuery and useMutation hooks that can be reused across any app in the monorepo.
Why This Approach
Section titled “Why This Approach”Key reasons:
- Code reuse: Query hooks can be shared between apps/web and future apps
- Type inference: Full type safety from Convex API through better-convex/cRPC
- Consistency: Single source of truth for API interactions
- Developer experience: Pre-built hooks with cache invalidation patterns
Alternatives considered:
- Keep hooks in apps/web: Would require duplication for new apps
- Generate hooks automatically: Added complexity without clear benefit
- Use Convex React hooks directly: Would lose TanStack Query benefits (caching, devtools)
Implementation Details
Section titled “Implementation Details”Package Structure
Section titled “Package Structure”packages/api/├── src/│ ├── context/ # CRPC context factory│ │ └── index.ts│ ├── things/ # Things API hooks│ │ ├── index.ts│ │ └── things.test.ts│ ├── types.ts # Re-exported types│ └── types.test.ts├── package.json├── tsconfig.json├── vitest.config.ts└── .oxlintrc.jsonExports
Section titled “Exports”The package provides three main exports:
// Context factory and types for app initializationimport { type Api, createCRPCContext } from "@repo/api/context"// Type-safe hooks for Things CRUDimport { useThings, useThingsCreate, useThingsList } from "@repo/api/things"// Re-exported types from backendimport type { ApiInputs, ApiOutputs, Thing } from "@repo/api/types"Context Setup
Section titled “Context Setup”Apps create their CRPC context by importing the factory and Convex API:
"use client"
import { api } from "@convex/api"import { meta } from "@convex/meta"import { type Api, createCRPCContext } from "@repo/api/context"
const crpcContext = createCRPCContext<Api>({ api, meta, convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,})
export const useCRPC = crpcContext.useCRPCexport const useCRPCClient = crpcContext.useCRPCClientexport const CRPCProvider = crpcContext.CRPCProviderNote: The app must import api and meta directly since they require path aliases that are app-specific. The @repo/api package provides the factory and types.
Available Hooks
Section titled “Available Hooks”Individual Hooks
Section titled “Individual Hooks”// List all thingsconst { data: things, isPending } = useThingsList(crpc, { limit: 10 })
// Get single thingconst { data: thing } = useThingsGet(crpc, thingId)
// Mutations with custom callbacksconst createThing = useThingsCreate(crpc, { onSuccess: () => invalidate() })const updateThing = useThingsUpdate(crpc, { onSuccess: () => invalidate() })const deleteThing = useThingsRemove(crpc, { onSuccess: () => invalidate() })
// Image upload URL generationconst generateUrl = useThingsGenerateUploadUrl(crpc)Bundled Hook
Section titled “Bundled Hook”The useThings hook provides all operations with automatic cache invalidation:
const { things, isLoading, error, create, update, remove, generateUploadUrl, invalidate } = useThings(crpc)
// Create with automatic cache invalidationawait create.mutateAsync({ title: "New Thing" })
// Updateawait update.mutateAsync({ id: thingId, title: "Updated" })
// Deleteawait remove.mutate({ id: thingId })Key Dependencies
Section titled “Key Dependencies”@tanstack/react-query: ^5.90.20 - Query/mutation state managementbackend: workspace:* - Convex API types and cRPC utilitiesbetter-convex: ^0.5.7 - Type-safe cRPC for Convex
Integration with Existing Code
Section titled “Integration with Existing Code”The package integrates with the existing better-convex/cRPC architecture:
- Backend: Exports API types through
backend/types - @repo/api: Provides hooks that consume these types
- Apps: Use hooks with their local CRPC context
packages/backend/├── convex/shared/types.ts # ApiInputs, ApiOutputs├── convex/shared/react.ts # createCRPCContext re-export└── convex/functions/_generated/api.d.ts
packages/api/├── src/context/index.ts # createApiContext (wraps createCRPCContext)├── src/things/index.ts # Hooks using the types└── src/types.ts # Re-exports for convenience
apps/web/└── lib/convex/crpc.tsx # App-specific context instanceContext for AI
Section titled “Context for AI”When working with @repo/api:
- All hooks require a
crpcclient fromuseCRPC()as the first argument - Hooks return standard TanStack Query results (data, isPending, error, etc.)
- The
useThingshook bundles CRUD operations with automatic cache invalidation - Individual hooks (useThingsList, etc.) offer more control over mutation callbacks
- Types are re-exported from
@repo/api/typesfor convenience - Context creation requires
NEXT_PUBLIC_CONVEX_SITE_URLenvironment variable
Outcomes
Section titled “Outcomes”Before
Section titled “Before”Query hooks were defined inline in component files:
const { data: things } = useQuery(crpc.things.list.queryOptions({}))const createThing = useMutation(crpc.things.create.mutationOptions({ onSuccess }))Hooks are imported from the shared package:
import { useThings } from "@repo/api/things"
const { things, create, update, remove } = useThings(crpc)Testing/Verification
Section titled “Testing/Verification”# Run API package teststurbo test --filter=@repo/api
# Type checkturbo check-types --filter=@repo/api
# Verify web app still worksturbo check-types --filter=webExpected results:
- 7 tests pass in @repo/api
- Type checking passes for all packages
- Web app builds without errors
Next Steps
Section titled “Next Steps”When adding new entities to the backend:
- Create hooks in
packages/api/src/<entity>/index.ts - Export from
package.json - Add types to
packages/api/src/types.ts - Update consuming apps to use the new hooks
Related Documentation
Section titled “Related Documentation”- Better Convex Migration - cRPC setup
- Better Convex Folder Structure - Backend organization
- Shared Validators - Input validation schemas