Skip to content

Project Structure

Diátaxis type: Reference — information-oriented, look up conventions and file responsibilities.

Table of Contents


Directory Layout

scratchy/
├── .github/
│   └── instructions/              # AI coding assistant instruction files
│       ├── react.instructions.md
│       ├── qwik.instructions.md
│       ├── drizzle.instructions.md
│       ├── trpc.instructions.md
│       ├── fastify.instructions.md
│       ├── typescript.instructions.md
│       ├── worker-threads.instructions.md
│       ├── vite.instructions.md
│       └── tailwindcss.instructions.md
├── docs/                          # Framework documentation and guides
├── drizzle/                       # Generated migration files (DO NOT EDIT)
├── src/
│   ├── server.ts                  # Fastify server setup and plugin registration
│   ├── index.ts                   # Application entry point
│   ├── config.ts                  # Environment and configuration loading
│   ├── router.ts                  # tRPC initialization, middleware, procedures
│   ├── context.ts                 # tRPC context creation
│   ├── db/                        # Database layer
│   │   ├── index.ts               # Drizzle instance and connection pool
│   │   ├── my-schema.ts           # PostgreSQL schema namespace
│   │   ├── schema/                # Table definitions (one file per entity)
│   │   │   ├── index.ts           # Barrel export of all schemas
│   │   │   ├── columns.helpers.ts # Shared column definitions (timestamps)
│   │   │   ├── user.ts            # User, account, session tables
│   │   │   ├── post.ts            # Posts table
│   │   │   └── ...                # Additional entity schemas
│   │   ├── queries/               # Module-scoped prepared statements
│   │   │   ├── users.ts
│   │   │   └── posts.ts
│   │   └── mutations/             # Write operations
│   │       ├── users.ts
│   │       └── posts.ts
│   │
│   ├── routers/                   # tRPC routers (internal API)
│   │   ├── index.ts               # Aggregate all routers into appRouter
│   │   ├── users/
│   │   │   ├── queries.ts         # User query procedures
│   │   │   └── mutations.ts       # User mutation procedures
│   │   └── posts/
│   │       ├── queries.ts
│   │       └── mutations.ts
│   │
│   ├── routes/                    # Fastify REST routes (external API)
│   │   ├── health/
│   │   │   └── index.ts           # GET /health
│   │   └── external/
│   │       └── api/
│   │           └── v1/
│   │               └── products/
│   │                   └── index.ts  # REST endpoints with CORS
│   │
│   ├── plugins/                   # Fastify plugins
│   │   ├── external/              # Third-party plugins
│   │   │   ├── cors.ts            # CORS configuration
│   │   │   ├── helmet.ts          # Security headers
│   │   │   └── rate-limit.ts      # Rate limiting
│   │   └── app/                   # Application plugins
│   │       ├── database.ts        # Database connection
│   │       ├── cache.ts           # Redis cache
│   │       ├── auth.ts            # Authentication
│   │       └── worker-pool.ts     # Piscina worker pool
│   │
│   ├── hooks/                     # Fastify lifecycle hooks
│   │   └── request-timer.ts       # Request duration logging
│   │
│   ├── renderer/                  # Worker Thread rendering
│   │   ├── pool.ts                # Piscina pool configuration
│   │   ├── worker.ts              # Worker entry point
│   │   └── templates/             # HTML shell templates
│   │       └── index.html
│   │
│   ├── client/                    # Client-side code (bundled by Vite)
│   │   ├── entry.ts               # Client entry point
│   │   ├── components/            # UI components
│   │   │   ├── qwik/              # Pure Qwik components
│   │   │   ├── react/             # React components (with qwikify$ wrappers)
│   │   │   └── shared/            # Shared types and utilities
│   │   ├── routes/                # File-based routing (Qwik City)
│   │   │   ├── layout.tsx         # Root layout
│   │   │   ├── index.tsx          # Home page (/)
│   │   │   ├── error.tsx          # Root error boundary (catches unhandled errors)
│   │   │   ├── not-found.tsx      # 404 page
│   │   │   ├── global-error.tsx   # Fatal error page (root layout failures)
│   │   │   ├── admin/
│   │   │   │   ├── layout.tsx     # Admin layout (with onRequest auth guard)
│   │   │   │   ├── index.tsx      # Admin dashboard
│   │   │   │   ├── loading.tsx    # Loading skeleton for admin section
│   │   │   │   └── error.tsx      # Error boundary for admin section
│   │   │   └── [slug]/
│   │   │       └── index.tsx      # Dynamic routes
│   │   ├── styles/                # Tailwind CSS
│   │   │   └── global.css
│   │   └── lib/                   # Client utilities
│   │       ├── trpc.client.ts     # tRPC client configuration
│   │       └── utils.ts           # Shared utilities
│   │
│   ├── lib/                       # Shared server utilities
│   │   ├── cache.ts               # async-cache-dedupe with Redis
│   │   ├── constants.ts           # Time, size, and other constants
│   │   ├── errors.ts              # createError(), ErrorResponse, notFound()
│   │   ├── logging.ts             # Pino logger configuration
│   │   ├── session.ts             # createCookie(), createSessionStorage()
│   │   └── worker-redis.ts        # Redis-based worker communication
│   │
│   └── types/                     # TypeScript type augmentations
│       └── fastify.d.ts           # Fastify module augmentation

├── public/                        # Static assets (served by Vite)
│   ├── favicon.ico
│   └── robots.txt

├── templates/                     # CLI scaffolding templates
│   ├── model.ts.hbs
│   ├── router.ts.hbs
│   ├── route.ts.hbs
│   └── component.tsx.hbs

├── tests/                         # Integration and E2E tests
│   ├── integration/
│   └── e2e/

├── AGENTS.md                      # AI agent guidance
├── LICENSE                        # MIT License
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.ts
├── drizzle.config.ts
└── .gitignore

Naming Conventions

Files and Folders

  • All files and folders use kebab-case (hyphenated lowercase)
    • user-profile.tsx, api-router.ts, worker-pool.ts
    • UserProfile.tsx, apiRouter.ts, workerPool.ts

Code

ElementConventionExample
Files/Folderskebab-caseuser-profile.tsx, db-queries/
ComponentsPascalCaseUserProfile, NavigationBar
VariablescamelCaseuserName, isAuthenticated
FunctionscamelCasecreateUser, findPostById
ConstantsUPPER_SNAKEMAX_RETRIES, DEFAULT_TIMEOUT
DB Tables/Columnssnake_caseuser_profile, created_at
Types/InterfacesPascalCaseUser, PostCreateInput
Enums (const obj)PascalCaseUserRole, PostStatus

Import Conventions

ContextExtension RuleExample
Server (Node.js)Always use .js extensionimport { db } from "~/db/index.js"
Client (Vite)Omit extensionsimport { Button } from "~/components/button"
Node.js built-insUse node: prefiximport { join } from "node:path"
npm packagesNo extensionimport Fastify from "fastify"

Key File Responsibilities

Entry Points

FilePurpose
src/index.tsApplication bootstrap (starts server)
src/server.tsFastify server creation and configuration
src/client/entry.tsClient-side application entry
src/renderer/worker.tsWorker thread entry point

Configuration Files

FilePurpose
tsconfig.jsonTypeScript compiler options
vite.config.tsVite bundler configuration
tailwind.config.tsTailwind CSS theme and plugins
drizzle.config.tsDrizzle Kit migration configuration
src/config.tsRuntime environment configuration

Type Augmentations

FilePurpose
src/types/fastify.d.tsExtends Fastify types with decorators

Route File Conventions

Scratchy uses special file names in src/client/routes/ inspired by Next.js App Router and Qwik City conventions:

FilePurposePattern Source
index.tsxPage component for the routeQwik City
layout.tsxShared layout wrapping child routesQwik City
loading.tsxLoading skeleton shown while page data loadsNext.js
error.tsxError boundary for the route segmentNext.js
not-found.tsx404 page when notFound() is thrownNext.js
global-error.tsxFatal error fallback (root layout failure)Next.js

Route Module Exports

Route files (index.tsx, layout.tsx) can export special functions:

typescript
// Data loading (runs on server before render)
export const useProductData = routeLoader$(async (event) => {
  return await findProductById.execute({ id: event.params.id });
});

// Server actions (handle form submissions)
export const useAddToCart = routeAction$(
  async (data, event) => {
    /* ... */
  },
  zod$({ productId: z.string(), quantity: z.number().min(1) }),
);

// Middleware (runs before loader/action)
export const onRequest: RequestHandler = async (event) => {
  // Authentication, logging, etc.
  await event.next();
};

// HTTP method-specific middleware
export const onGet: RequestHandler = async (event) => {
  /* ... */
};
export const onPost: RequestHandler = async (event) => {
  /* ... */
};

// Page metadata
export const head: DocumentHead = {
  title: "Product Page",
  meta: [{ name: "description", content: "Product details" }],
};

Adding New Features

Adding a New Database Entity

  1. Create schema: src/db/schema/<entity>.ts
  2. Export from barrel: src/db/schema/index.ts
  3. Create queries: src/db/queries/<entity>.ts
  4. Create mutations: src/db/mutations/<entity>.ts
  5. Create tRPC router: src/routers/<entity>/queries.ts + mutations.ts
  6. Register router: src/routers/index.ts
  7. Generate migration: pnpm drizzle-kit generate

Adding a New tRPC Router

  1. Create directory: src/routers/<domain>/
  2. Create queries.ts with query procedures
  3. Create mutations.ts with mutation procedures
  4. Register in src/routers/index.ts

Adding a New REST Endpoint

  1. Create directory: src/routes/external/api/v1/<resource>/
  2. Create index.ts with route handlers
  3. Register CORS if needed for cross-origin access

Adding a New Qwik Page

  1. Create file in src/client/routes/<path>/index.tsx
  2. Export default component using component$()
  3. Add routeLoader$ for data loading if needed
  4. Add routeAction$ for form handling if needed
  5. Add onRequest middleware for auth guards if needed
  6. Optionally add loading.tsx for streaming skeleton
  7. Optionally add error.tsx for error boundary

Adding Middleware to a Route

  1. Export onRequest from a layout.tsx or index.tsx file
  2. Use event.sharedMap to pass data to loaders and components
  3. Call event.next() to continue the chain
  4. Throw event.redirect() to redirect
  5. See middleware.md for full patterns

Adding a New Component

  1. Qwik component: src/client/components/qwik/<name>.tsx
  2. React component: src/client/components/react/<name>.tsx (with qwikify$ wrapper)

Adding a New Fastify Plugin

  1. Create file in src/plugins/app/<name>.ts or src/plugins/external/<name>.ts
  2. Wrap with fp() from fastify-plugin
  3. Add TypeScript augmentation in src/types/fastify.d.ts if adding decorators