Skip to content
advanced Phase 8 · TypeScript Project

Project Architecture

Plan a full stack TypeScript project with proper folder structure.

1h
0 problems
Topic Progress 0%

Project Architecture

Project Architecture

Directory Structure

src/
├── config/
│   ├── env.ts          # Environment variables
│   ├── database.ts     # Database configuration
│   └── index.ts        # Config barrel
├── types/
│   ├── index.ts        # Shared types
│   ├── api.ts          # API request/response types
│   └── models.ts       # Domain model types
├── middleware/
│   ├── auth.ts         # Authentication middleware
│   ├── validation.ts   # Request validation
│   └── error-handler.ts
├── services/
│   ├── user-service.ts
│   ├── email-service.ts
│   └── index.ts
├── repositories/
│   ├── user-repository.ts
│   └── base-repository.ts
├── routes/
│   ├── user-routes.ts
│   └── index.ts
├── utils/
│   ├── logger.ts
│   └── helpers.ts
└── index.ts            # Entry point

Layered Architecture

Routes (HTTP Layer)
  ↓
Controllers (Request/Response)
  ↓
Services (Business Logic)
  ↓
Repositories (Data Access)
  ↓
Database

Type-First Design

// types/models.ts - define types first
export interface User {
  id: string;
  name: string;
  email: string;
  role: UserRole;
  createdAt: Date;
  updatedAt: Date;
}

export type UserRole = 'admin' | 'user' | 'guest';

export interface CreateUserDTO {
  name: string;
  email: string;
  role?: UserRole;
}

export interface UpdateUserDTO {
  name?: string;
  email?: string;
  role?: UserRole;
}

Build Configuration

Build Configuration

TypeScript Config

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "sourceMap": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

package.json

{
  "name": "my-ts-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "typecheck": "tsc --noEmit",
    "prepare": "husky"
  },
  "dependencies": {
    "express": "^4.18.0",
    "zod": "^3.22.0",
    "dotenv": "^16.3.0"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "tsx": "^4.7.0",
    "vitest": "^1.2.0",
    "@types/node": "^20.11.0",
    "@types/express": "^4.17.0",
    "eslint": "^8.56.0",
    "prettier": "^3.2.0",
    "husky": "^9.0.0"
  }
}

API Contract Types

API Contract Types

Request/Response Types

// types/api.ts
export interface ApiResponse<T> {
  data: T;
  meta?: {
    page: number;
    limit: number;
    total: number;
  };
}

export interface ApiError {
  error: string;
  code: string;
  details?: Record<string, string[]>;
}

export interface PaginationQuery {
  page?: string;
  limit?: string;
  sort?: string;
  order?: 'asc' | 'desc';
}

export interface SearchQuery extends PaginationQuery {
  q?: string;
}

Route Handler Types

// types/routes.ts
import { Request, Response, NextFunction } from 'express';

export type Handler<
  Params = {},
  ResBody = unknown,
  ReqBody = unknown,
  ReqQuery = {}
> = (
  req: Request<Params, ResBody, ReqBody, ReqQuery>,
  res: Response<ResBody>,
  next: NextFunction
) => Promise<void> | void;

// Usage
type GetUserHandler = Handler<
  { id: string },         // Params
  ApiResponse<User>,      // ResBody
  {},                     // ReqBody
  {}                      // ReqQuery
>;

Coding Conventions

Coding Conventions

Naming Conventions

// Interfaces - PascalCase, no prefix
interface User { }
interface CreateUserDTO { }

// Type aliases - PascalCase
type UserRole = 'admin' | 'user';
type Result<T> = Success<T> | Failure;

// Enums - PascalCase
direction enum Status {
  Active = 'active',
  Inactive = 'inactive'
}

// Functions - camelCase, verb first
function createUser() { }
function fetchUsers() { }

// Constants - camelCase or UPPER_SNAKE_CASE
const maxRetries = 3;
const API_TIMEOUT = 5000;

// Files - kebab-case
// user-service.ts
// auth-middleware.ts

Type Patterns

// Use interfaces for public APIs
interface UserServiceInterface {
  findById(id: string): Promise<User | null>;
  create(dto: CreateUserDTO): Promise<User>;
}

// Use type aliases for unions and computed types
type RequestHandler = (req: Request, res: Response) => Promise<void>;

// Prefer readonly for immutable data
type ReadonlyUser = Readonly<User>;

// Use branded types for IDs
type UserId = string & { readonly __brand: unique symbol };
type OrderId = string & { readonly __brand: unique symbol };