This document describes the architecture, file system structure, and naming conventions for the web application to ensure consistency and maintainability.
The architecture is divided into five logical layers. Each layer has a clearly defined responsibility. Dependencies always point inwards (from UI towards Infrastructure).
| Name | Description |
|---|---|
| UI | Renders the user interface (Pages & Components). |
| Hooks | Encapsulates client-side state and interaction logic. |
| Endpoints | Defines the public-facing API for clients (e.g., GraphQL). |
| Domain | Contains the core business logic, models, and rules (Zod). |
| Infrastructure | Manages external concerns like the database. |
We use a Vertical Slicing architecture. The code is not grouped by technical layers but by business features (Business Domains). It increases cohesion and simplifies maintainability.
Each feature lives in its own folder (e.g., /services/text-documents/).
Within this folder, it is further divided by Use Cases (Actions) (e.g., /get-text-document/).
/services/text-documents/text-document.model.tsget-text-document/get-text-document.service.tsget-text-document.infrastructure.tsget-text-document.resolver.tsui/get-text-document.query.tsuse-get-text-document.ts
Consistent naming is crucial for the readability and discoverability of code.
Files within a use-case folder follow the pattern [action].[layer].ts.
- get-text-document.service.ts
- get-text-document.infrastructure.ts
- get-text-document.resolver.ts
Hooks are named according to the pattern use[Action].ts.
- use-get-text-document.ts
- use-create-text-document.ts
Zod schemas and their inferred types live in files following the pattern [resource].model.ts.
- text-document.model.ts
- user.model.ts
type is preferred over interface to ensure a consistent and flexible definition of data structures.
A consistent logging strategy is essential for monitoring, debugging, and understanding the application's behavior. My approach follows the architectural layers to provide a clear trace of every request.
Log messages must follow the consistent pattern "[Context]: [Action]" to ensure readability and easy filtering.
Error handling follows a clear, cross-layer pattern to ensure predictable and secure API responses.
- Domain Layer: Throws specific, custom error classes (e.g.,
TextDocumentNotFoundError) to clearly name business errors. - Endpoint Layer: Catches these specific errors and uses a
createApiErrorhelper to translate them into standardizedGraphQLErrorobjects for the client. - Global Errors: Unexpected system errors are caught by a global handler, logged, and returned to the client as a generic
INTERNAL_SERVER_ERRORto avoid leaking internal details.
Since this project uses MongoDB (schema-less) with Prisma, we distinguish between schema definition changes and data migrations.
- Modify the
schema.prismafile. - Run
npx prisma generateto update the Prisma Client. - Run
npx prisma migrate dev --name <migration-name>to create a history of the schema change in theprisma/migrationsfolder. This command does not alter the MongoDB data.
To apply schema changes to existing data in production without causing downtime, a two-phase deployment is required. The migrate-mongo library is used to manage and execute data migration scripts.
Phase 1: Deploy Tolerant Code
- Adjust Zod Schema: Make the validation schemas in the
.model.tsfiles tolerant of both the old and new data structure (e.g., by using.optional()). - Deploy to Vercel: Commit and deploy this tolerant version of the application.
Phase 2: Migrate Data & Deploy Strict Code
- Create Migration Script: Use
npm run migrate:create <migration-name>to generate a new migration file in themigrations/directory. Fill it with the necessary data transformation logic. - Run Production Migration: After the tolerant code is live, run the migration against the production database from your local machine. The workflow is streamlined by the Vercel CLI:
# 1. (IMPORTANT) Create a backup of the production database. # 2. Pull production environment variables into a temporary file. vercel env pull .env.production --environment=production # 3. Run the migration. npm run migrate:prod # 4. (IMPORTANT) Delete the temporary environment file. rm .env.production
- Revert to Strict Schema: Change the Zod schemas back to be strict, enforcing the new data structure (e.g., using
z.discriminatedUnion). - Final Deploy: Commit and deploy this final, strict version of the application to Vercel.