Project Structure
Understanding where every file lives and why helps you navigate the codebase confidently. This document explains every directory and its architectural role.
Top-level layout
1
2
3
4
5
6
7
8
9
10
11
12
13
street/
├── package.json # npm manifest, "type": "module"
├── tsconfig.json # TypeScript strict config, NodeNext ESM
├── street-build.sh # One-command build script
├── Dockerfile # Multi-stage build → distroless runtime
├── .github/
│ └── workflows/
│ └── ci-cd.yml # GitHub Actions: build, test, docker
├── src/ # All TypeScript source
├── tests/ # Integration tests (compiled alongside src/)
├── migrations/ # Plain SQL migration files
├── uploads/ # Runtime upload destination (gitignored)
└── dist/ # Compiled JavaScript output (gitignored)
Source directory: src/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
src/
├── main.ts # Application entry point — CLI or HTTP boot
├── reflect-shim.d.ts # Global Reflect.metadata type declarations
│
├── core/ # Framework kernel
│ ├── types.ts # Shared TypeScript type utilities
│ ├── context.ts # StreetContext — per-request object
│ ├── container.ts # IoC container + @Injectable
│ └── decorators.ts # @Controller, @Get, @Post, @Validate, @Config, @Command
│
├── http/ # HTTP layer
│ ├── server.ts # streetApp() factory — HTTP server + body parser
│ ├── exceptions.ts # Typed HTTP exceptions (400, 401, 404 ...)
│ ├── openapi.ts # OpenAPI 3.1 spec generator
│ └── auth.middleware.ts# JWT auth, CORS, security headers middleware
│
├── router/
│ └── router.ts # Compiled regex router, validation, pipeline runner
│
├── database/
│ ├── wire.ts # PostgreSQL wire protocol v3 client
│ ├── pool.ts # Bounded connection pool
│ ├── repository.ts # Generic IRepository<T> + StreetPostgresRepository<T>
│ └── migrations.ts # SQL migration runner with tracking table
│
├── security/
│ ├── jwt.ts # HMAC-SHA256 JWT (sign, verify, decode)
│ ├── session.ts # AES-256-GCM session encryption
│ ├── vault.ts # KEK-based config decryption
│ ├── ratelimit.ts # Sliding-window rate limiter
│ └── xss.ts # Recursive XSS sanitizer
│
├── multipart/
│ └── parser.ts # Streaming multipart/form-data parser
│
├── websocket/
│ ├── server.ts # WebSocket server (ws), heartbeat, broadcast
│ └── sse.ts # Server-Sent Events wrapper
│
├── cache/
│ └── lru.ts # LRU cache with TTL and bounded entry count
│
├── telemetry/
│ └── tracker.ts # Request telemetry, heap profiling, latency P50/P99
│
├── cluster/
│ └── coordinator.ts # node:cluster coordinator, IPC heartbeat, auto-restart
│
├── webhook/
│ └── dispatcher.ts # Async webhook queue, HMAC-signed HTTP dispatch
│
├── cli/
│ ├── kernel.ts # CLI kernel, parseArgv, CliKernel class
│ └── commands.ts # @Command implementations (migrate, user:*)
│
├── config/
│ └── index.ts # AppConfig class, @Config-decorated fields
│
├── domain/
│ └── user.ts # User entity, DTOs, validation schemas
│
├── services/
│ ├── user.service.ts # UserService — business logic
│ └── user.repository.ts# UserRepository — typed DB access
│
└── controllers/
├── user.controller.ts # UserController — HTTP handler methods
└── health.controller.ts# /api/health, /api/metrics, /api/openapi.json
Dependency flow
The architecture follows a strict one-directional dependency graph. No layer imports from a layer above it:
1
2
3
4
5
6
7
8
9
10
11
12
main.ts
└── config/ (loads environment)
└── database/pool (connects to PostgreSQL)
└── services/ (business logic)
└── database/repository (data access)
└── database/wire (wire protocol)
└── controllers/ (HTTP handlers)
└── services/ (calls service methods)
└── core/context (reads/writes request)
└── http/server (routes to controllers)
└── router/ (compiles and matches routes)
└── core/container (resolves controller instances)
This means:
- Controllers never talk to
wire.tsdirectly - Services never import from
http/ - The router never knows about PostgreSQL
This separation makes every layer independently testable and replaceable.
The migrations/ directory
1
2
3
4
5
migrations/
├── 001_create_users.sql # Forward migration
├── 001_create_users.rollback.sql # Rollback (drop)
├── 002_create_sessions_webhooks.sql
└── 002_create_sessions_webhooks.rollback.sql
Migrations are plain SQL files, sorted lexicographically. The leading timestamp prefix ensures correct execution order. Every migration has a companion .rollback.sql for safe rollback operations.
The migration runner tracks applied migrations in a street_migrations table. Re-running migrate is idempotent — already-applied migrations are skipped.
The tests/ directory
1
2
tests/
└── integration.test.ts # Full integration tests, node:test + node:assert
Tests live outside src/ but are compiled alongside it (both are included in tsconfig.json). This means:
- Tests are type-checked with the same strict rules as source
- Tests import from
../src/...with full type safety - Tests can be run immediately after
tscwithnode --test
There are no unit test stubs or mock frameworks. Every test runs against a real PostgreSQL instance and a real HTTP server.
The uploads/ directory
File uploads are streamed directly to this directory by the multipart parser. It must exist before the server starts — street-build.sh creates it automatically.
In production, mount this as a Docker volume or replace it with a cloud storage sink:
1
docker run -v /data/uploads:/app/uploads my-app
The dist/ directory
Never edit files in dist/. It is entirely regenerated on every tsc run. It is excluded from git via .gitignore.
1
2
3
dist/
├── src/ # Compiled source
└── tests/ # Compiled tests
Naming conventions
| Pattern | Example | Rule |
|---|---|---|
| Files | user.service.ts |
<noun>.<role>.ts |
| Classes | UserService |
PascalCase |
| Interfaces | IRepository<T> |
I prefix for contracts |
| Types | ParsedFile |
PascalCase |
| Constants | MAX_BODY_BYTES |
SCREAMING_SNAKE |
| Private fields | _config |
_ prefix for unused-but-injected |
| Decorators | @Injectable() |
Applied in declaration order |
Adding a new feature
To add a new resource (e.g. Product), follow this checklist:
1
2
3
4
5
6
1. src/domain/product.ts — entity, DTOs, validation schemas
2. src/services/product.repository.ts — extends StreetPostgresRepository<Product>
3. src/services/product.service.ts — business logic, @Injectable
4. src/controllers/product.controller.ts — @Controller, route handlers
5. migrations/003_create_products.sql — CREATE TABLE
6. Register controller in src/main.ts
That is the complete change set. No framework configuration, no module registration files, no factory wiring.