skills.nabinkhair.com.np

Product Stack

Full-stack Next.js architecture for product engineers. A strict 9-layer pattern covering database schemas (Drizzle), API routes, Axios services, React Query hooks, Zod validation, and shadcn/ui components. Every new feature follows the same flow so the codebase stays consistent as it scales. Use when adding CRUD resources, API routes, React Query hooks, Drizzle schemas, or any full-stack feature to a Next.js app.

install


      npx skills add nabinkhair42/nk-skills --skill product-stack
      
    
---
name: product-stack
description: Full-stack Next.js architecture for product engineers. A strict 9-layer pattern covering database schemas (Drizzle), API routes, Axios services, React Query hooks, Zod validation, and shadcn/ui components. Every new feature follows the same flow so the codebase stays consistent as it scales. Use when adding CRUD resources, API routes, React Query hooks, Drizzle schemas, or any full-stack feature to a Next.js app.
---

Product Stack

A full-stack Next.js architecture for shipping CRUD-heavy applications fast without sacrificing consistency. Built around Next.js App Router, Drizzle ORM, Axios, React Query, Zod, and shadcn/ui.

The core idea: every feature follows the same 9-layer flow. Database to UI, no shortcuts.

Backend flow: DB Schema -> Route Handlers -> Server Services -> Response Helpers Frontend flow: Endpoints -> Services -> Hooks -> Components

Full code lives in reference/api.md (Layers 1–8) and reference/db.md (Layer 9). Read those when writing code for a layer; this file defines the rules.


Project Structure

src/
├── app/
│   ├── (marketing)/              # Public pages
│   ├── dashboard/                # Protected pages
│   ├── api/                      # API route handlers
│   │   └── {resource}/
│   │       ├── route.ts          # Collection: GET (list), POST (create)
│   │       └── [id]/route.ts     # Item: GET, PUT, DELETE
│   ├── actions/                  # Server Actions
│   ├── layout.tsx
│   └── globals.css

├── config/
│   ├── axios.ts                  # Axios instance + interceptors
│   ├── api-endpoints.ts          # All API paths + React Query keys
│   ├── constants.ts              # Enums, storage keys, feature flags
│   └── query-client.ts           # TanStack Query client defaults

├── services/
│   ├── {resource}.service.ts     # Frontend: Axios calls to API routes
│   └── server/                   # Server-only: called from route handlers
│       └── {service}.ts

├── hooks/
│   └── use-{resource}.ts         # React Query hooks wrapping services

├── schemas/
│   └── {resource}.ts             # Zod validation + inferred types

├── types/
│   └── {resource}.ts             # TypeScript interfaces

├── components/
│   ├── ui/                       # shadcn/ui primitives (never hand-edit)
│   ├── loaders/                  # Skeletons, spinners
│   ├── dialogs/                  # All modal/dialog components
│   └── {feature}/                # Feature-scoped components

├── providers/
│   ├── root-provider.tsx         # Composes all providers
│   ├── theme-provider.tsx
│   └── query-provider.tsx        # QueryClientProvider setup

├── db/
│   ├── index.ts                  # Drizzle instance + connection
│   ├── schema/
│   │   ├── index.ts              # Barrel export
│   │   ├── columns.ts            # Reusable column helpers
│   │   ├── auth.ts               # User, session, account tables
│   │   └── {resource}.ts         # Domain tables with enums + indexes
│   └── migrations/               # Generated by drizzle-kit

├── lib/
│   ├── utils.ts                  # cn() and shared formatters
│   ├── response/
│   │   └── server-response.ts    # successResponse, errorResponse, Errors.*
│   └── middleware/
│       └── api-middleware.ts     # protectedApi, adminApi wrappers

├── drizzle.config.ts             # Drizzle Kit configuration (project root)
└── auth.ts                       # Auth configuration

The 9 Layers

Each layer has one job. Full code per layer is in the reference files.

1. DB Schema — db/schema/{resource}.ts

Drizzle tables with enums, indexes, $inferSelect/$inferInsert types (reference/db.md).

  • Every table spreads shared timestamps
  • Index every FK column — Drizzle does not auto-index them
  • onDelete always explicit (cascade or set null)
  • Types derived from the schema, never hand-written

2. Endpoints — config/api-endpoints.ts

All paths + React Query keys in ONE file (reference/api.md).

  • Static paths for collections, function paths (id) => ... for items
  • QUERY_KEYS mirrors endpoint structure
  • Never construct API paths outside this file

3. Zod Schema — schemas/{resource}.ts

Validation AND types from one schema.

  • z.infer<typeof schema> for all input types — never manual interfaces
  • Derive update schemas with .partial()
  • z.transform() + .pipe() for normalization

4. Route Handlers — app/api/{resource}/route.ts + [id]/route.ts

Thin: validate → call server service → respond via helpers (reference/api.md).

  • Wrap with protectedApi / adminApi middleware — never raw handlers
  • schema.safeParse() every request body before processing
  • Return only via successResponse, paginatedResponse, or Errors.*
  • await ctx.params (Promise in Next.js 15+)

5. Service — services/{resource}.service.ts

Thin axios wrappers. One method per endpoint.

  • Returns response.data; typed with ApiResponse<T>
  • No error handling, no toasts, no side effects

6. Hook — hooks/use-{resource}.ts

React Query wraps services; ALL cache management lives here.

  • List hooks accept a params object (useProjects({ page, limit })) so sort/filter params compose later (see data-table-pattern)
  • Single-item queries use enabled: !!id
  • queryKey always from QUERY_KEYS factory; every param that affects the result goes in the key
  • Cache strategy is either/or per mutation: default invalidateQueries; if the flow adopts optimistic-cache-pattern, switch fully to setQueriesData — never both in one onSuccess
  • TanStack Query v5: isPending, not isLoading, on mutations

7–9. Components — components/{resource}/

Columns file → Table wrapper → Dialogs (create/edit/delete), using the hook.

  • Components call hooks, never services
  • Loaders/skeletons in components/loaders/
  • For forms follow form-stack; for tables follow data-table-pattern

Server Actions vs API Routes

If your frontend is the only consumer, prefer Server Actions. If anything else calls it (mobile app, third party, webhooks), use an API route. Example in reference/api.md.

Use case Pattern
Form submission from your app Server Action
Simple create/update/delete from a dialog Server Action
External API consumed by mobile app or third party API Route Handler
Webhook endpoint API Route Handler
Cacheable GET endpoint API Route Handler
Complex multi-step mutation with streaming API Route Handler

Server Actions must still re-validate with the same Zod schema — client validation is UX, server validation is the gate.

Server Components by Default

Fetch data directly in Server Components with Drizzle — do not call your own API routes from the server. Pass initial data to client components as props. Keep "use client" at the leaf. Details in reference/api.md.


Adding a New Feature Checklist

When you need to add a new resource (e.g. “tasks”), follow this exact order:

  1. DB Schema - Define table in db/schema/task.ts with enums, indexes, and $inferSelect/$inferInsert types. Export from db/schema/index.ts. Run drizzle-kit generate + drizzle-kit migrate
  2. Endpoints - Add TASKS to API_ENDPOINTS and QUERY_KEYS in config/api-endpoints.ts
  3. Zod Schema - Create schemas/task.ts with request/form validation + inferred input types
  4. Types - Add interfaces to types/task.ts if needed beyond Zod and Drizzle inference
  5. Route - Create app/api/tasks/route.ts (GET list, POST create) and app/api/tasks/[id]/route.ts (GET, PUT, DELETE)
  6. Service - Create services/task.service.ts with CRUD methods using axios
  7. Hook - Create hooks/use-tasks.ts with useQuery/useMutation wrapping the service
  8. Components - Build UI in components/tasks/ using the hook
  9. Loaders - Add skeleton in components/loaders/ for the new views

Never skip a layer. Components never call services directly. Services never show toasts. Hooks never construct URLs. Route handlers never return raw NextResponse.json() without the response helpers.

For spec-driven features, write a spec first with feature-spec.


Tech Stack

  • Next.js 15+ (App Router, Server Components, Server Actions) - framework
  • TypeScript - strict mode
  • PostgreSQL + Drizzle ORM - database + type-safe queries
  • shadcn/ui - UI components (never hand-edit components/ui/)
  • TanStack React Query v5 - client-side server state management
  • Axios - HTTP client with interceptors (for client -> API route calls)
  • Zod - schema validation + type inference
  • Pill Toaster - toast notifications (pnpm dlx shadcn@latest add https://toast.nabinkhair.com.np/r/pill-toaster.json; mount <Toaster /> in the root provider)

Common Mistakes

  1. Hardcoded API paths - Always use API_ENDPOINTS, never /api/projects as a string in services
  2. Calling services from components - Use hooks. Components should only know about hooks
  3. Manual TypeScript types for form data - Use z.infer<typeof schema>, never duplicate types
  4. Toast in services - Services are data-only. Toast belongs in hook onSuccess/onError
  5. Raw NextResponse.json in routes - Use successResponse(), Errors.*(), paginatedResponse()
  6. Missing query invalidation - Every mutation must update cache OR invalidate — pick one strategy, never both (see Layer 6 rules)
  7. Fetching with empty ID - Always use enabled: !!id on single-item queries
  8. Business logic in components - Put it in server services or route handlers
  9. Forgetting .safeParse() - Always validate request body in route handlers before processing
  10. Creating axios instances per service - One instance in config/axios.ts, import everywhere
  11. Missing FK indexes - Drizzle does not auto-index foreign keys. Add index() for every FK column or joins will full-scan
  12. Using API routes for simple internal mutations - Use Server Actions when the frontend is the only consumer
  13. "use client" too high in the tree - Keep it as close to the leaf as possible. Server Components reduce client JS by up to 70%
  14. Forgetting await on params - In Next.js 15+, params and searchParams in page/route components are Promises
  15. Using isLoading from mutations - TanStack Query v5 renamed it to isPending