Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
240144c
feat: add @thaitype/ioctopus dependency and create project documentation
mildronize Mar 23, 2025
530d4d6
docs: update README to clarify usage of forked version of @thaitype/i…
mildronize Mar 23, 2025
3520a6b
feat: add initial directory structure for core modules and shared res…
mildronize Mar 23, 2025
0a1c4dd
feat: update documentation and configuration for shared resources and…
mildronize Mar 23, 2025
60c84d0
feat: create core domain module with initial TypeScript setup and ESL…
mildronize Mar 23, 2025
d4409cb
feat: restructure shared library with new configurations, add shared …
mildronize Mar 23, 2025
1871532
misc: run lint fix
mildronize Mar 23, 2025
cb07a05
feat: add template project with ESLint, Prettier, TypeScript, and Vit…
mildronize Mar 23, 2025
c645e58
feat: update core domain module with new ESLint and Prettier configur…
mildronize Mar 23, 2025
beb9eee
feat: create application module with initial TypeScript, ESLint, and …
mildronize Mar 23, 2025
8c1a5ff
feat: update user repository imports, add infrastructure module with …
mildronize Mar 23, 2025
e08a3c5
feat: add interface-adapters module with initial TypeScript, ESLint, …
mildronize Mar 23, 2025
3d7b8f3
feat: enhance application and interface-adapters modules with updated…
mildronize Mar 23, 2025
bc8c9b9
feat: refactor application and interface-adapters modules to streamli…
mildronize Mar 23, 2025
6778d1b
feat: add CLI module with initial TypeScript, ESLint, Prettier setup,…
mildronize Mar 23, 2025
27cbe09
feat: add TypeScript as a dev dependency and update pnpm lockfile
mildronize Mar 23, 2025
dfa1c8b
feat: update Vitest TypeScript configuration to refine exclusion patt…
mildronize Mar 23, 2025
43c45e2
feat: update VSCode settings to exclude node_modules and .turbo, remo…
mildronize Mar 24, 2025
58c64c5
Merge branch 'main.architect.remove-src' into main.architect
mildronize Mar 24, 2025
c02d04b
refactor: refactor shared library with initial implementation, tests…
mildronize Mar 24, 2025
755af92
feat: add initial project setup with shared library, configuration fi…
mildronize Mar 24, 2025
af45894
feat: rename tp @acme/mono tool
mildronize Mar 24, 2025
aa95993
feat: update package dependencies and add script for linting commands
mildronize Mar 24, 2025
beff4a8
feat: update project configuration and dependencies, remove vitest co…
mildronize Mar 24, 2025
725fc54
feat: add shared library implementation with tests and vitest configu…
mildronize Mar 24, 2025
1baa7f9
feat: update CLI scripts to use mono commands and add initial index file
mildronize Mar 24, 2025
8512018
feat: add vitest configuration and update CLI command handling
mildronize Mar 24, 2025
63e4708
chore: move prettier from package level into root workspace
mildronize Mar 24, 2025
482e29f
fix: standardize import quotes and ensure consistent export formattin…
mildronize Mar 24, 2025
d2f1d64
refactor: rename lint script and update formatting commands for consi…
mildronize Mar 24, 2025
acd5f9d
chore: remove format check and fix scripts from package.json files
mildronize Mar 24, 2025
e1fc200
fix: update check-types script to use mono for type checking
mildronize Mar 24, 2025
2ef42a0
refactor: simplify command execution and add utility functions for ar…
mildronize Mar 24, 2025
e25a66f
feat: add mono CLI and update package configurations for improved scr…
mildronize Mar 25, 2025
6882dde
format: code
mildronize Mar 25, 2025
e86519d
chore: clean up package dependencies in pnpm-lock.yaml and package.json
mildronize Mar 25, 2025
1777d31
chore: remove unused dependency on @acme/mono from package.json
mildronize Mar 25, 2025
580ade0
refactor: rename configs package pattern to start with config
mildronize Mar 25, 2025
3b98517
chore: remove unused dependencies from package.json in interface-adap…
mildronize Mar 25, 2025
53ca00e
docs: update README to reflect new project structure and template usage
mildronize Mar 25, 2025
5a2985a
docs: update README to clarify package creation and toolchain usage
mildronize Mar 25, 2025
4d06289
docs: remove backup README and add detailed database and future proje…
mildronize Mar 25, 2025
8f2ce8e
docs: add references and resources for monorepo and clean architectur…
mildronize Mar 25, 2025
8922104
docs: enhance README with IoC container details and add Q&A documenta…
mildronize Mar 25, 2025
a737ed7
Migrate the project use central build toolchain using mono internal p…
mildronize Mar 25, 2025
60bd766
Migrate to T3 Stack with Users Domain in Clean Architecture (#3)
mildronize May 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/test-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- main

env:
NODE_VERSION: 20.18.x
NODE_VERSION: 22.14.x
# PNPM_VERSION: 10.5.x

jobs:
Expand All @@ -35,14 +35,13 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint
run: pnpm lint:check

# - name: Run tests
# run: pnpm test

- name: Prepare .env file
run: cp apps/nextjs/.env.default apps/nextjs/.env

# Ignore build until fix server action build in Next.js
# - name: Build project
# run: pnpm build
- name: Build project
run: pnpm build
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ yarn-error.log*
dist
generated

.env
.env

coverage.json

tsconfig.tsbuildinfo

.nx
1 change: 1 addition & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm lint:check
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
{
"mode": "auto"
}
]
],
"files.exclude": {
"**/.turbo": true,
}
}
272 changes: 206 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,117 +1,257 @@
# TypeScript Clean Architecture
# 🧱 Clean Architecture Template for TypeScript Monorepos

[![Test and Build](https://github.com/thaitype/typescript-clean-architecture/actions/workflows/test-and-build.yml/badge.svg)](https://github.com/thaitype/typescript-clean-architecture/actions/workflows/test-and-build.yml)

> In progess!
This is a **Clean Architecture starter template** designed for monorepos using **Turborepo** + **pnpm** + **TypeScript**. It's simple enough to get started quickly and scalable enough to grow into a large production system.

This is turborepo starter with Drizzle ORM and PostgreSQL pre-configured.
---

> [!NOTE]
> This example uses `pnpm` as package manager.
## ⚡ Quick Start

## Using this example
```bash
git clone https://github.com/your-org/your-repo.git
cd your-repo
pnpm install
pnpm dev
```

Clone the repository:
Read all document in [docs](./docs) folder.

```sh
git clone https://github.com/htsh-tsyk/turbo-drizzle.git
```
---

## What's inside?
## 🧠 What is Clean Architecture?

This Turborepo includes the following packages/apps:
**Clean Architecture** is a way to structure your application so that:

### Apps and Packages
- Business logic is **independent** from frameworks (e.g. Express, Next.js)
- Code is **testable**, **modular**, and easy to **extend**
- Dependencies always point **inward**, from outer layers toward the core

- `web`: another [Next.js](https://nextjs.org/) app
- `@repo/database`: Drizzle ORM wrapper to manage & access your database
- `@repo/ui`: a stub React component library shared by a `web` application
- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
- `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
## 🤔 Why This Project Helps You

Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
By leveraging Clean Architecture, this template enables you to:

### Utilities
- ✨ Write framework-agnostic business logic
- 🧪 Test use cases in isolation (no DB or HTTP server needed)
- 🧱 Structure your code for long-term maintainability
- 🧩 Easily swap implementations (e.g. Mongo → Postgres, REST → GraphQL)
- 👥 Onboard teammates faster with a predictable, layered system

This Turborepo has some additional tools already setup for you:
---

- [TypeScript](https://www.typescriptlang.org/) for static type checking
- [ESLint](https://eslint.org/) for code linting
- [Prettier](https://prettier.io) for code formatting
- [Drizzle for database ORM](https://orm.drizzle.team/) for database ORM
## 🛠 Usage (Root-Level Scripts)

### Build
The root `package.json` includes common scripts powered by `turbo`:

To build all apps and packages, run the following command:

```
cd turbo-drizzle
cp apps/web/.env.default apps/web/.env
pnpm install
pnpm build
```
### Script Descriptions

### Database
- `dev`: Run all development servers/command in parallel,
- `build`: Build all packages respecting their dependency graph
- `test`: Run tests across all workspaces, including coverage test
- `test:watch`: Watch and re-run tests interactively
- `lint:check`: Run lint and type checks
- `lint:fix`: Automatically fix lint issues
- `format`: Check Prettier formatting
- `format:fix`: Auto-format using Prettier
- `test:coverage-report`: Run Show summary coverage report for all packages

We use [Drizzle ORM](https://orm.drizzle.team/) to manage & access our database. As such you will need a database for this project, either locally or hosted in the cloud.
## How to run package in specific package

To make this process easier, we offer a [`docker-compose.yml`](https://docs.docker.com/compose/) file to deploy a PostgreSQL server locally with a new database named `repo_development` (To change this update the `POSTGRES_DB` environment variable in the `docker-compose.yml` file):
For example to run only `cli` package:

```bash
cd turbo-drizzle
docker-compose up -d
pnpm run dev --filter=cli
```

Once deployed you will need to copy the `.env.default` file to `.env` in order for Drizzle to have a `DATABASE_URL` environment variable to access.
---

## 🔰 Template Project Overview

This project follows a modular monorepo layout:

```bash
cp apps/web/.env.default apps/web/.env
.
├── apps/ # Applications (UI, CLI, etc)
│ ├── nextjs/ # Next.js frontend (App Router)
│ └── cli/ # CLI tools (e.g., for batch scripts)
├── core/ # Clean architecture layers
│ ├── domain/ # Business entities, value objects
│ ├── application/ # Use cases, interfaces
│ ├── interface-adapters/ # Controllers, adapters, presenters
│ ├── infrastructure/ # Implementations (e.g., repositories)
│ └── di/ # Dependency injection setup
├── packages/ # Modular services
│ ├── database-drizzle/ # DB setup with Drizzle ORM
│ └── shared/ # Cross-cutting shared utils
├── configs/ # Centralized configuration presets
│ ├── config-eslint/ # Shared ESLint config
│ ├── config-typescript/ # Shared TSConfig presets
│ └── config-vitest/ # Shared Vitest setup
├── tools/ # Toolchain scripts and helpers
│ ├── db/ # DB migration + seed tooling
│ ├── mono/ # CLI wrapper for build/test/lint/dev
│ └── template/ # Reusable project template package
├── turbo.json # Task orchestration config
└── pnpm-workspace.yaml # Defines workspace structure
```

If you added a custom database name, or use a cloud based database, you will need to update the `DATABASE_URL` in your `.env` accordingly.
---

Once deployed & up & running, you will need to create & deploy migrations to your database to add the necessary tables. This can be done using [Drizzle Migrate](https://orm.drizzle.team/docs/migrations):
## 📦 Template Generator

in `database` package: (command `drizzle-kit generate`)
To create a new package, copy and rename the `tools/template` folder:

```bash
cp -r tools/template core/new-package
```
pnpm generate

This includes:
- Preconfigured build/dev/test scripts
- Standard tooling via `mono`
- `tsconfig`, `eslint`, `vitest` setup
- Minimal boilerplate with `lib` + test examples

Just update the package name in `package.json` and start coding!

---

## 🔧 Build Tooling with Turborepo + Mono

### 🧩 Centralized Toolchain via `tools/mono`

We built a custom toolchain named `mono` to easily manage and control the build process across all packages.

Instead of installing build tools like `esbuild`, `vitest`, and `eslint` in every package, we centralize them via:

- `tools/mono`: Unified CLI for commands like `dev`, `test`, `build`
- `configs/*`: Shared config presets for linting, TS, and testing

Example `mono` script:
```ts
const scripts: MonoScripts = {
'lint:check': 'eslint src',
'lint:fix': 'eslint src --fix',
'test': 'vitest run',
'test:watch': 'vitest watch',
'build': 'esbuild ./src/index.ts --bundle --minify --platform=node --outfile=dist/index.js',
'dev': 'tsx watch ./src/index.ts',
'start': 'tsx ./src/index.ts',
'check-types': 'tsc --noEmit',
};
```

```bash
pnpm turbo db:migrate
### 🧪 How packages use `mono`

Each package reuses the `mono` CLI by mapping local scripts:

```json
{
"scripts": {
"dev": "mono dev",
"start": "mono start",
"build": "mono build",
"test": "mono test",
"test:watch": "mono test:watch",
"lint:check": "mono lint:check",
"lint:fix": "mono lint:fix",
"check-types": "mono check-types"
},
"devDependencies": {
"@acme/mono": "workspace:*",
"@acme/config-eslint": "workspace:*",
"@acme/config-typescript": "workspace:*",
"@acme/config-vitest": "workspace:*"
}
}
```

An optional additional step is to seed some initial or fake data to your database.
### 🛠 Root `package.json` dependencies

Tools are only installed once at the root:

```json
{
"devDependencies": {
"turbo": "^2.4.4",
"@vitest/coverage-istanbul": "^3.0.9",
"typescript": "^5.8.2",
"eslint": "^9.22.0",
"esbuild": "^0.25.1",
"prettier": "^3.5.3",
"vitest": "^3.0.9"
}
}
```

To do this update check the seed script located in `packages/database/scripts/seed.ts` & add or update any users you wish to seed to the database.
### 🧠 Workspace definition

Once edited run the following command to run tell Drizzle to run the seed script defined in the Drizzle configuration:
```yaml
# pnpm-workspace.yaml
packages:
- "apps/*"
- "core/*"
- "packages/*"
- "configs/*"
- "tools/*"
```

```bash
pnpm turbo db:seed
---

## 🔗 High-Level Dependency Diagram

```mermaid
graph TD

A[domain] --> B[application]
B --> C[interface-adapters]
B --> D[infrastructure]
C --> E[di]
D --> E
E --> F[apps/nextjs]
E --> G[apps/cli]
H[packages/database-drizzle] --> D
I[packages/shared] --> B
I --> C
I --> D
I --> E
I --> F
```

### Develop
---

To develop all apps and packages, run the following command:
## 🧠 Dependency Injection

```shell
pnpm dev
This template uses [`@thaitype/ioctopus`](https://www.npmjs.com/package/@thaitype/ioctopus) — a simple, metadata-free IoC container for TypeScript that works across runtimes (Node, Edge, etc).

However, this project use a forked version of `ioctopus` when the original package is fully support type-safety, this project will switch back to the original package, see [issue#3](https://github.com/thaitype/ioctopus/issues/3)

To resolve any service:

```ts
import { getInjection } from "@acme/di";
const userController = getInjection("UserController");
```

## Useful Links
---

Happy coding! ✨ Let your architecture evolve, not collapse. 🏗️

Learn more about the power of Turborepo:
---

- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)
## Read More
- Basic Concept of Monorepo by Turborepo: <https://turbo.build/repo/docs/guides/tools/typescript>
- Clean Architecture: <https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html>

## References
- Drizzle Turbo Repo Template: https://github.com/htsh-tsyk/turbo-drizzle/tree/main
- Next.js Clean Architecture: https://github.com/nikolovlazar/nextjs-clean-architecture/tree/main
- Next.js 15 on turborepo template: https://github.com/vercel/turborepo/tree/c59da312df134cc1aaf7c269bc3cd0b78c073b07/examples/basic

Some codes template bring the idea from those repositories:
- Drizzle Turbo Repo Template: <https://github.com/htsh-tsyk/turbo-drizzle>
- Next.js Clean Architecture: <https://github.com/nikolovlazar/nextjs-clean-architecture>
- Next.js 15 on turborepo template: <https://github.com/vercel/turborepo/tree/c59da312df134cc1aaf7c269bc3cd0b78c073b07/examples/basic>
- Vitest on Turbo Repo: <https://github.com/vercel/turborepo/tree/c59da312df134cc1aaf7c269bc3cd0b78c073b07/examples/with-vitest>
1 change: 1 addition & 0 deletions apps/cli/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MONGO_URI=mongodb://root:example@localhost:27017/test-db?authSource=admin
4 changes: 4 additions & 0 deletions apps/cli/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { config } from "@acme/config-eslint/base";

/** @type {import("eslint").Linter.Config} */
export default config;
8 changes: 8 additions & 0 deletions apps/cli/lib/shared.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect, it, describe } from 'vitest';
import { shared } from './shared';

describe('shared', () => {
it('should work', () => {
expect(shared()).toEqual('shared');
});
});
3 changes: 3 additions & 0 deletions apps/cli/lib/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function shared(): string {
return 'shared';
}
Loading