Hype StackHypeStack

How to Organize Features

Backend code is organized by domain. Each domain in src/features/ owns its business logic, database access, schemas, and tests.

Structure

src/features/<domain>/
  modules/
    <use-case>/
      index.ts           # Module function
      index.test.ts      # Tests
      schemas/            # Zod schemas for this module
      helpers/            # Module-specific helpers
      emails/             # Email templates (React Email)
  db/
    queries/              # Kysely read queries
    mutations/            # Prisma writes, Kysely transactions
  types/                  # Domain types
  constants/              # Domain constants
  websockets/
    emitters.ts           # wsEmit calls for this domain

Example: projects domain

src/features/projects/
  modules/
    create-project/
      index.ts
      index.test.ts
    get-project/
      index.ts
    delete-project/
      index.ts
    projects-list/
      index.ts
  db/
    queries/
      get-by-id.query.ts
      get-many.query.ts
    mutations/
      create-project.mutation.ts
      update-project.mutation.ts
      delete-project.mutation.ts

Rules

  • No src/services/ directory. Shared infrastructure goes in src/libs/, database clients in src/db/, cache in src/cache/, middleware in src/middleware/.

  • Routes stay thin. Route files in src/routes/ validate input, call module functions, and return responses. Logic belongs in modules.

  • No try/catch. The centralized error middleware handles all errors. Throw typed errors and let them bubble up. See the error handling section below.

Error handling

Use typed error classes from @backend/middleware/error:

ts
import { ApplicationError, ApplicationErrorCode } from "@backend/middleware/error";

if (!project) {
  throw new ApplicationError({
    code: ApplicationErrorCode.NOT_FOUND,
    message: "Project not found",
    statusCode: 404,
  });
}

Available error classes:

ClassUse case
ApplicationErrorDomain failures (not found, bad request, invalid state)
AuthErrorAuthentication/session failures
AuthorizationErrorMissing permissions
ValidationErrorSchema validation (usually thrown automatically by the validate middleware)

The global error middleware formats these into consistent HTTP responses.