Skip to content

Commit a6fd7a4

Browse files
Layered Architecture (#185)
### Description <!-- Provide a comprehensive description here about what your PR aims to solve. --> <!-- You may also add additional context --> Refactor the project to Layered Architecture. For context, see [ARCHITECTURE.md](https://github.com/agnyz/bedstack/blob/layered/ARCHITECTURE.md) and [PROJECT_STRUCTURE.md](https://github.com/agnyz/bedstack/blob/layered/PROJECT_STRUCTURE.md). For roadmap, see https://github.com/agnyz/bedstack/milestone/3. --- ### PR Checklist <!-- Please do not remove this section --> <!-- Mark each item with an "x" ([ ] becomes [x]) --> - [x] Read the Developer's Guide in [CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md) - [x] Use a concise title to represent the changes introduced in this PR - [x] Provide a detailed description of the changes introduced in this PR, and, if necessary, some screenshots - [x] Reference an issue or discussion where the feature or changes have been previously discussed - [x] Add a failing test that passes with the changes introduced in this PR, or explain why it's not feasible - [x] Add documentation for the feature or changes introduced in this PR to the docs; you can run them with `bun docs` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added detailed architecture and project structure documentation. - Introduced new controllers and modules for articles, comments, and profiles with RESTful endpoints. - Added comprehensive DTOs for API request validation and response formatting. - Implemented robust error handling with standardized error responses. - Added new database schemas and relations for articles, comments, favorites, tags, and user follows. - Introduced utility functions for validation, date formatting, and slug generation. - **Bug Fixes** - Improved type safety and error handling in service and repository layers. - Enhanced validation and ownership checks for comment and profile operations. - **Refactor** - Replaced plugin-based route registration with controller-based organization. - Migrated schema definitions to ORM-based models and updated import paths for consistency. - Removed legacy files and outdated type schemas for a cleaner codebase. - Simplified service constructors and unified error classes. - **Documentation** - Added detailed architecture and project structure documentation. - Updated formatting and consistency in various markdown files. - **Chores** - Updated dependencies and scripts for database operations and development tooling. - Introduced index files for consolidated exports in DTOs, interfaces, mappers, and utilities. - Updated TypeScript configuration for module resolution and path aliases. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 0f25046 commit a6fd7a4

File tree

170 files changed

+4800
-1797
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+4800
-1797
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
"password": "postgres"
1616
}
1717
],
18-
"cSpell.words": ["bedstack", "Elysia", "elysiajs", "favicons", "typesafe"]
18+
"cSpell.words": ["bedstack", "Elysia", "elysiajs", "favicons", "typesafe"],
19+
"typescript.tsdk": "node_modules/typescript/lib"
1920
}

ARCHITECTURE.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Architecture
2+
3+
## Overview
4+
5+
This service uses a modular **Layered Architecture** inspired by the [NestJS philosophy](https://docs.nestjs.com/#philosophy). We favor **separation of concerns** over minimalism to maintain clear boundaries between different parts of the system.
6+
7+
We separate the system into 3 main layers:
8+
9+
1. **Controller** – Talks to the client
10+
2. **Service** – Handles the business logic
11+
3. **Repository** – Interacts with the database
12+
13+
Each domain feature (e.g. `articles`, `profiles`, `tags`) is isolated into its own module, containing these layers plus:
14+
15+
- **Mapper** - Transforms data between layers
16+
- **Schema** - Defines database tables and relations
17+
18+
```mermaid
19+
graph TD
20+
subgraph Controller Layer
21+
C1[articles.controller.ts]
22+
C2[comments.controller.ts]
23+
C3[tags.controller.ts]
24+
end
25+
26+
subgraph Service Layer
27+
S1[articles.service.ts]
28+
S2[comments.service.ts]
29+
S3[tags.service.ts]
30+
end
31+
32+
subgraph Repository Layer
33+
R1[articles.repository.ts]
34+
R2[comments.repository.ts]
35+
R3[tags.repository.ts]
36+
end
37+
38+
subgraph Schema Layer
39+
SC1[articles.schema.ts]
40+
SC2[comments.schema.ts]
41+
SC3[tags.schema.ts]
42+
SC4[article-tags.schema.ts]
43+
end
44+
45+
subgraph Mapper Layer
46+
M1[articles.mapper.ts]
47+
M2[comments.mapper.ts]
48+
M3[tags.mapper.ts]
49+
end
50+
51+
C1 --> S1 --> R1 --> SC1
52+
C2 --> S2 --> R2 --> SC2
53+
C3 --> S3 --> R3 --> SC3
54+
55+
S1 --> M1
56+
S2 --> M2
57+
S3 --> M3
58+
59+
SC1 --> SC4
60+
SC3 --> SC4
61+
```
62+
63+
## Layer Responsibilities
64+
65+
### 1. Controller Layer (Client-facing)
66+
67+
- Receives data from the client (DTO)
68+
- Returns data to the client (DTO)
69+
- Validates data types
70+
- Calls the service layer
71+
- Can shape requests and responses, without performing any business logic
72+
73+
### 2. Service Layer (Business Logic)
74+
75+
- Contains the business logic
76+
- Can perform any kind of calculation or transformation as long as it's part of the business rules
77+
- Validates logic rules (e.g., checking if a user can register)
78+
- Handles errors and logging
79+
- Calls the repository layer to get or save data
80+
- Can receive controller-level DTOs, but must map or validate them before passing data to the repository
81+
82+
### 3. Repository Layer (Database Access)
83+
84+
- Talks to the database
85+
- Only responsible for saving and retrieving data
86+
- **No** assumptions about validation
87+
- **No** business logic should go here
88+
- Handles pagination, sorting, and other database-specific operations
89+
- Returns raw database rows, not domain entities
90+
91+
### Additional Layers
92+
93+
#### Mapper (Data Transformation)
94+
95+
- Transforms Row types from the database to domain entities or DTOs
96+
- Performs camelCase vs. snake_case mapping if needed
97+
- Converts Date to ISO strings for output, etc.
98+
99+
#### Schema (Database Definitions)
100+
101+
- Defines schemas using an ORM (e.g. `pgTable()` with Drizzle ORM and PostgreSQL)
102+
- Optionally defines table relations (e.g. `relations()` with Drizzle ORM)
103+
104+
## Type Design Principles
105+
106+
1. **Interfaces vs Classes**:
107+
108+
- Use interfaces (`IUser`) to define contracts between layers
109+
- Use classes (`User`) for concrete implementations. The (database) entity is a concrete implementation of the interface.
110+
- This separation allows for better testing and flexibility
111+
112+
2. **Canonical Forms**:
113+
114+
- Store canonical forms in the database (e.g., `birthdate`)
115+
- The canonical form is represented in the entity (`User`) _and_ the interface (`IUser`)
116+
- The DTO might use a different form, e.g. `CreateUserDto` might use `age` instead of `birthdate`
117+
- Use mappers to convert between forms
118+
119+
3. **System vs Domain Properties**:
120+
- System properties (`id`, `createdAt`, `updatedAt`) are managed by the base entity
121+
- Domain properties (e.g. `email`, `name`) are defined in the interface, enforced by the entity, and controlled by the DTOs
122+
123+
## Examples
124+
125+
### Example 1: Can register?
126+
127+
```typescript
128+
canRegister(user: Partial<IUser>) {
129+
if (user.email.endsWith('@banned.com')) {
130+
throw new ForbiddenException('Email domain is not allowed');
131+
}
132+
133+
if (!this.isOldEnough(user.birthdate)) {
134+
throw new ForbiddenException('User must be at least 13 years old');
135+
}
136+
}
137+
```
138+
139+
This check lives in the service layer because:
140+
141+
- It's business logic
142+
- It could change based on product decisions
143+
- It might be reused across different controllers (`signup`, `adminCreateUser`, etc.)
144+
- If tomorrow we add GraphQL on top of our REST, this logic will remain the same
145+
146+
### Example 2: Normalize email
147+
148+
```typescript
149+
normalizeEmail(email: string) {
150+
return email.toLowerCase().trim();
151+
}
152+
```
153+
154+
Also clearly service-level: it's a standardized rule, not controller-specific logic.
155+
156+
## See also
157+
158+
- More on **Project structure** - see [Project Structure](PROJECT_STRUCTURE.md)
159+
- **Contributing** - see [Developer's Guide](CONTRIBUTING.md)
160+
- **API Documentation** - see [RealWorld Backend Specifications](https://realworld-docs.netlify.app/specifications/backend/introduction/)
161+
- **Drizzle ORM Documentation** - see [Drizzle ORM](https://orm.drizzle.team/)

CODE_OF_CONDUCT.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
1717
Examples of behavior that contributes to a positive environment for our
1818
community include:
1919

20-
* Demonstrating empathy and kindness toward other people
21-
* Being respectful of differing opinions, viewpoints, and experiences
22-
* Giving and gracefully accepting constructive feedback
23-
* Accepting responsibility and apologizing to those affected by our mistakes,
20+
- Demonstrating empathy and kindness toward other people
21+
- Being respectful of differing opinions, viewpoints, and experiences
22+
- Giving and gracefully accepting constructive feedback
23+
- Accepting responsibility and apologizing to those affected by our mistakes,
2424
and learning from the experience
25-
* Focusing on what is best not just for us as individuals, but for the
25+
- Focusing on what is best not just for us as individuals, but for the
2626
overall community
2727

2828
Examples of unacceptable behavior include:
2929

30-
* The use of sexualized language or imagery, and sexual attention or
30+
- The use of sexualized language or imagery, and sexual attention or
3131
advances of any kind
32-
* Trolling, insulting or derogatory comments, and personal or political attacks
33-
* Public or private harassment
34-
* Publishing others' private information, such as a physical or email
32+
- Trolling, insulting or derogatory comments, and personal or political attacks
33+
- Public or private harassment
34+
- Publishing others' private information, such as a physical or email
3535
address, without their explicit permission
36-
* Other conduct which could reasonably be considered inappropriate in a
36+
- Other conduct which could reasonably be considered inappropriate in a
3737
professional setting
3838

3939
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
106106
### 4. Permanent Ban
107107

108108
**Community Impact**: Demonstrating a pattern of violation of community
109-
standards, including sustained inappropriate behavior, harassment of an
109+
standards, including sustained inappropriate behavior, harassment of an
110110
individual, or aggression toward or disparagement of classes of individuals.
111111

112112
**Consequence**: A permanent ban from any sort of public interaction within

CONTRIBUTING.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ Hey there! We're thrilled that you'd like to contribute to this project. Your he
99

1010
This project uses [Bun](https://bun.sh) as a runtime as well as a package manager. It's a modern, fast, and lightweight alternative to [Node.js](https://nodejs.org/en/) and [npm](https://www.npmjs.com/). To install Bun on POSIX systems (like Ubuntu or macOS), run the following command:
1111

12-
```sh
13-
curl -fsSL https://bun.sh/install | bash
14-
```
12+
```sh
13+
curl -fsSL https://bun.sh/install | bash
14+
```
1515

1616
Otherwise, visit the [Bun installation page](https://bun.sh/docs/installation) for installation options.
1717

@@ -27,7 +27,7 @@ Build the project for production. The result is under `dist/`.
2727

2828
### `bun check`
2929

30-
We use [Biome](https://biomejs.dev/) for **both linting and formatting**. It is an ultra-fast, Rust based linter and formatter.
30+
We use [Biome](https://biomejs.dev/) for **both linting and formatting**. It is an ultra-fast, Rust based linter and formatter.
3131
It also lints JSON.
3232

3333
You can also run `bun fix` to apply any safe fixes automatically.
@@ -69,8 +69,9 @@ Where the template is:
6969
```
7070

7171
Replacing:
72-
* `<keyword>` with one of `close`, `closes`, `closed`, `fix`, `fixes`, `fixed`, `resolve`, `resolves`, `resolved`
73-
* `<issue-number>`: the issue number you are fixing
72+
73+
- `<keyword>` with one of `close`, `closes`, `closed`, `fix`, `fixes`, `fixed`, `resolve`, `resolves`, `resolved`
74+
- `<issue-number>`: the issue number you are fixing
7475

7576
This will let GitHub know the issues are linked, and automatically close them once the PR gets merged. Learn more at [the guide](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword).
7677

GOVERNANCE.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,31 @@
44

55
To understand the scope of this document, please read:
66

7-
* [*What is governance?* by Adobe](https://github.com/adobe/open-development-template/blob/master/Governance.md#meritocracy)
8-
* [*Jenkins Governance Document* for an inspiration](https://www.jenkins.io/project/governance/)
7+
- [_What is governance?_ by Adobe](https://github.com/adobe/open-development-template/blob/master/Governance.md#meritocracy)
8+
- [_Jenkins Governance Document_ for an inspiration](https://www.jenkins.io/project/governance/)
99

1010
## Who we are
1111

1212
We are a group of open-source developers who develop, use, promote the Elysia RealWorld example app, software around it, and other related activities for our mutual benefit.
1313

1414
## Core Philosophy
1515

16-
* **Lower barrier to entry**
16+
- **Lower barrier to entry**
1717

18-
Everyone is welcome to contribute, regardless of their background, experience, or identity. We value all contributions, and we are committed to providing a friendly, safe, and welcoming environment for all. Everyone gets a voice, and everyone is listened to.
18+
Everyone is welcome to contribute, regardless of their background, experience, or identity. We value all contributions, and we are committed to providing a friendly, safe, and welcoming environment for all. Everyone gets a voice, and everyone is listened to.
1919

20-
* **Seeking consensus**
20+
- **Seeking consensus**
2121

22-
We seek consensus among contributors, and we are open to new ideas and approaches. When consensus cannot be reached after giving the stage to all parties, we will use a majority-wins voting process. We will strive to resolve conflicts in a constructive manner.
22+
We seek consensus among contributors, and we are open to new ideas and approaches. When consensus cannot be reached after giving the stage to all parties, we will use a majority-wins voting process. We will strive to resolve conflicts in a constructive manner.
2323

24-
* **Meritocracy**
24+
- **Meritocracy**
2525

26-
We value contributions based on their technical merit, and we welcome new contributors based on their demonstrated ability to contribute. Valuable contributers will be granted access to private channels and will be able to participate in the decision-making process.
27-
28-
* **Transparency**
29-
30-
While the decision-making process is not always public, the results of the decision making process must be public. Decisions will be made after thoughtful consideration of the community's input through [Discord](https://discord.gg/8UcP9QB5AV) and [GitHub Discussions](https://github.com/bedtime-coders/bedstack/discussions).
26+
We value contributions based on their technical merit, and we welcome new contributors based on their demonstrated ability to contribute. Valuable contributers will be granted access to private channels and will be able to participate in the decision-making process.
3127

32-
* **Automation**
28+
- **Transparency**
3329

34-
We rely on automation to make our processes more efficient and to reduce the burden on contributors. We will automate as much as possible, and we will strive to make our automation tools accessible to all contributors. The goal is to abstract away the mundane tasks and let contributors focus on the fun stuff.
30+
While the decision-making process is not always public, the results of the decision-making process must be public. Decisions will be made after thoughtful consideration of the community's input through [Discord](https://discord.gg/8UcP9QB5AV) and [GitHub Discussions](https://github.com/bedtime-coders/bedstack/discussions).
31+
32+
- **Automation**
33+
34+
We rely on automation to make our processes more efficient and to reduce the burden on contributors. We will automate as much as possible, and we will strive to make our automation tools accessible to all contributors. The goal is to abstract away the mundane tasks and let contributors focus on the fun stuff.

0 commit comments

Comments
 (0)