Skip to content

Directory Architecture

Overall Layout

arrange/
โ”œโ”€โ”€ heineken-interview-arrange-backend/    # Bun + Hono REST API
โ”œโ”€โ”€ heineken-interview-arrange-frontend/   # Next.js App Router admin console
โ””โ”€โ”€ heineken-interview-arrange-docs/       # This documentation (MkDocs)

The two applications are coupled only through OpenAPI: the backend generates /openapi.json from its route definitions, and the frontend generates React Query hooks and types from it with Orval.

Backend

heineken-interview-arrange-backend/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app.ts             # Hono app assembly (CORS, OpenAPI, Swagger UI, 404/error)
โ”‚   โ”œโ”€โ”€ server.ts          # Entry point
โ”‚   โ”œโ”€โ”€ error.ts           # Domain-specific AppError subclasses
โ”‚   โ”œโ”€โ”€ routes/
โ”‚   โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”‚   โ””โ”€โ”€ v1/            # HTTP layer; createRoute() pairs an OpenAPI definition with its handler
โ”‚   โ”œโ”€โ”€ schemas/           # Zod schemas for requests/responses (public fields are snake_case)
โ”‚   โ”œโ”€โ”€ services/          # Business logic
โ”‚   โ”œโ”€โ”€ repositories/      # Drizzle queries only
โ”‚   โ”œโ”€โ”€ db/
โ”‚   โ”‚   โ””โ”€โ”€ schema.ts      # Single source of truth for all tables, enums and domain types
โ”‚   โ”œโ”€โ”€ lib/               # response / errors / logger / pagination / date / databricks-client, etc.
โ”‚   โ”œโ”€โ”€ middlewares/       # protected-route / error-handler / request-id / winston-logger
โ”‚   โ”œโ”€โ”€ config/            # env / database / google / business-hours
โ”‚   โ”œโ”€โ”€ types/             # Import related types
โ”‚   โ””โ”€โ”€ batch/             # Batches that run independently of the API (import / databricks-meta-sync / pii-cleanup)
โ”œโ”€โ”€ __tests__/
โ”‚   โ””โ”€โ”€ unit/services/     # Vitest unit tests
โ”œโ”€โ”€ scripts/               # setup.sh / migrate.ts / seed.ts
โ”œโ”€โ”€ drizzle.config.ts
โ”œโ”€โ”€ docker-compose.yml     # PostgreSQL 17
โ”œโ”€โ”€ Dockerfile             # App (Lambda + aws-lambda-adapter)
โ”œโ”€โ”€ Dockerfile.migration   # Migration Lambda
โ””โ”€โ”€ Dockerfile.sync        # Sync batch (ECS Fargate)

Layering

A one-way dependency: routes โ†’ services โ†’ repositories โ†’ Drizzle.

Directory Responsibility What it must not do
src/routes/v1/ HTTP layer: OpenAPI definitions, auth and role control, snake_case โ†” camelCase conversion Contain business logic or call Drizzle directly
src/schemas/ Zod schemas. Public API fields are snake_case Expose raw DB column names
src/services/ Business logic, re-exported as namespaces from services/index.ts via export * as xxxService Depend on Hono's Context
src/repositories/ Drizzle queries only; failures wrapped in DatabaseQueryError and friends Make business decisions
src/db/schema.ts Single source of truth for tables, enums and $inferSelect / $inferInsert types Be split across multiple files

ESM extensions

Imports must carry the .js extension (from '../lib/index.js'), even when pointing at TypeScript sources.

Where auth and roles are applied

Per router, never in individual handlers.

candidatesRoutes.use('*', protectedRoute());
candidatesRoutes.on(['POST', 'PATCH', 'PUT', 'DELETE'], ['/*'], requireRole('member'));

Frontend

heineken-interview-arrange-frontend/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app/
โ”‚   โ”‚   โ”œโ”€โ”€ (dashboard)/          # Authenticated admin console
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ page.tsx          # Dashboard
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ projects/         # Project list, creation, detail (arrange flow)
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ surveys/          # Survey browsing
โ”‚   โ”‚   โ”œโ”€โ”€ login/                # Login
โ”‚   โ”‚   โ””โ”€โ”€ scheduling/[token]/   # Public candidate page (unauthenticated)
โ”‚   โ”œโ”€โ”€ api/                      # Orval output. Never edit by hand
โ”‚   โ”‚   โ”œโ”€โ”€ custom-fetch.ts       # Adds Bearer token, returns { data, status, headers }
โ”‚   โ”‚   โ”œโ”€โ”€ endpoints/            # Hooks grouped by tag
โ”‚   โ”‚   โ””โ”€โ”€ models/               # Type definitions
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ ui/                   # Generic UI (button / table / modal / slot-calendar, etc.)
โ”‚   โ”‚   โ”œโ”€โ”€ layout/               # app-layout / header / sidebar
โ”‚   โ”‚   โ”œโ”€โ”€ projects/             # arrange-steps / step1-form / dashboard-stats
โ”‚   โ”‚   โ”œโ”€โ”€ step2/                # segment-sidebar / candidate-table / question-filter
โ”‚   โ”‚   โ”œโ”€โ”€ candidates/           # candidate-row / summary-bar / status-helpers
โ”‚   โ”‚   โ””โ”€โ”€ providers.tsx         # Shared providers such as React Query
โ”‚   โ”œโ”€โ”€ lib/                      # auth / utils / candidate-status / response-filter
โ”‚   โ””โ”€โ”€ types/
โ”‚       โ””โ”€โ”€ arrange-settings.ts   # Arrange settings type, duplicated with the backend
โ”œโ”€โ”€ orval.config.ts
โ”œโ”€โ”€ next.config.ts
โ””โ”€โ”€ Dockerfile

Directory notes

Directory Description
src/app/(dashboard)/ Authenticated console. A route group, so it does not appear in the URL
src/app/scheduling/[token]/ Public page reachable with a token only; no authentication
src/api/ Orval output. Named after paths, e.g. useGetApiV1ProjectsProjectId
src/components/ui/ Reusable UI components
src/lib/ The single gateway for auth tokens (auth.ts) plus utilities
src/types/ Types not expressible via OpenAPI (JSON column contents, etc.)

Reading API responses

custom-fetch.ts returns { data, status, headers }, and that data is the API envelope ({ success, data, ... }), so screens end up traversing response.data.data. Use the apiData() helper in lib/utils.ts.

const { data } = useGetApiV1Projects();
const projects = apiData<GetApiV1Projects200>(data);
// projects?.data โ†’ Project[]

Where to Put New Files

Backend

What you are adding Where it goes
A new endpoint src/routes/v1/<resource>.ts (extend an existing router, or create one and register it in routes/v1/index.ts)
Request/response definitions src/schemas/<resource>.ts
Business logic src/services/<resource>.ts, re-exported from services/index.ts
DB queries src/repositories/<resource>.ts
Tables, enums, domain types src/db/schema.ts (single file)
Environment variables Both src/config/env.ts and .env.example
Generic errors src/lib/errors.ts
Domain-specific errors src/error.ts
Batches src/batch/<name>.ts

Frontend

What you are adding Where it goes
A screen src/app/(dashboard)/**/page.tsx
Generic UI component src/components/ui/
Screen-specific component src/components/<screen>/
API calls Do not add them โ€” regenerate src/api/** with bun run generate-api
Type definitions src/types/