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.

Sponsor open source

Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!

Sponsor on GitHub