Skip to content

Commit f621349

Browse files
committed
feat: added copilot instructions
1 parent 7267e38 commit f621349

File tree

8 files changed

+343
-9
lines changed

8 files changed

+343
-9
lines changed

.github/copilot-instructions.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Coding Guidelines for Node.js/TypeScript Boilerplate
2+
3+
## Project Overview
4+
5+
This repository is a boilerplate for building Node.js applications using TypeScript, following best practices and architectural patterns. It is designed to be a starting point for new projects, providing a solid foundation with modern development tools and methodologies.
6+
7+
## Code Architecture
8+
9+
### Key Patterns
10+
- **CQRS (Command Query Responsibility Segregation)**: Separates read and write operations for better scalability and maintainability.
11+
- **Dependency Injection**: Uses Awilix for managing dependencies, promoting loose coupling and easier testing.
12+
- **Event-driven architecture**: Utilizes an event dispatcher for handling domain events, enabling asynchronous processing and decoupling of components.
13+
- **Feature-based structure**: Organizes code into features, each encapsulating related functionality, making it easier to navigate and maintain.
14+
- **REST API**: Exposes a RESTful API for client interactions, following standard HTTP methods and status codes.
15+
16+
### Project Structure
17+
```src/
18+
├── app/
19+
│ ├── features/ # Feature-based organization
20+
│ │ ├── [feature-name]/ # Each feature has its own directory
21+
│ │ │ ├── actions/ # REST endpoint handlers
22+
│ │ │ ├── commands/ # State-changing DTOs
23+
│ │ │ ├── queries/ # Data-retrieval DTOs
24+
│ │ │ ├── handlers/ # Command/query business logic
25+
│ │ │ ├── query-handlers/ # Query business logic
26+
│ │ │ ├── events/ # Domain events
27+
│ │ │ ├── subscribers/ # Event listeners
28+
│ │ │ ├── graphql/ # GraphQL resolvers
29+
│ │ │ ├── models/ # TypeORM entities
30+
│ │ │ └── routing.ts # Route definitions
31+
│ ├── infrastructure/ # Infrastructure code (e.g., repositories implementations)
32+
│ ├── config/ # Configuration files
33+
│ ├── container/ # Dependency injection container setup
34+
│ ├── errors/ # Custom error classes
35+
│ ├── shared/ # Shared utilities and helpers
36+
│ ├── tests/ # Integration tests files
37+
│ │ └── bootstrap.ts # Test bootstrap file
38+
│ └── migrations/ # TypeORM migrations
39+
├── src/
40+
│ ├── index.ts # Application entry point
41+
│ ├── server.ts # Server setup and configuration
42+
│ ├── container.ts # Dependency injection container setup
43+
│ ├── config.ts # Application configuration
44+
│ ├── logger.ts # Logging setup
45+
│ ├── database.ts # Database connection setup
46+
│ └── shared/ # Shared utilities and helpers
47+
```
48+
49+
### Key Libraries and Functionalities
50+
- **TypeScript**: For static typing and modern JavaScript features.
51+
- **Express**: Web framework for building REST APIs.
52+
- **TypeORM**: ORM for database interactions.
53+
- **Awilix**: Dependency injection container.
54+
- **@tshio/command-bus**: Command bus for handling commands.
55+
- **@tshio/query-bus**: Query bus for handling queries.
56+
- **@tshio/event-dispatcher**: Event dispatcher for handling domain events.
57+
- `src/shared/pagination-utils`: Utility functions for request parameters to TypeORM options conversion and list endpoint responses.
58+
- **UUID v4**: For unique identifiers in database entities.
59+
- **Docker**: For containerization and consistent development environment.
60+
- **Jest**: For unit and integration testing.
61+
- **Celebrate**: For input validation in REST endpoints.
62+
63+
### Key Development Guidelines
64+
65+
#### Important commands
66+
- **Linting**: Use `npm run lint` to check code style.
67+
- **Fixing Lint Issues**: Use `npm run lint-fix` to automatically fix lint issues.
68+
- **Formatting**: Use `npm run format` to format code according to the project's style guide.
69+
- **Running Tests**: Use `npm run integration` for integration tests and `npm run units` for unit tests.
70+
- **Generating Migrations**: Use `npm run generate-migration` to create new migrations. Do not run migrations manually, they are auto-executed at application start.
71+
- **Running migrations**: Use `npm run migrations` to run all pending migrations.
72+
73+
#### General Guidelines
74+
- Always use TypeScript for type safety and modern JavaScript features.
75+
- Follow the project's coding conventions and architecture patterns.
76+
- Use dependency injection for managing dependencies, avoiding direct instantiation of classes.
77+
- Never run any commands with `docker-compose` command, use npm scripts instead (e.g., `npm run lint`, `npm run lint-fix`, `npm run forma`).
78+
79+
#### Naming Guidelines
80+
- Always think in terms of features, not technical layers.
81+
- Features names should be descriptive and reflect the bounded context (e.g., `planning`, `ordering`, `management`). Avoid naming features by connected entities (e.g., `user`, `product`).
82+
- Use **kebab-case** for directories, filenames and imports (e.g., `user.controller.ts`).
83+
- Use **camelCase** for variables and function names (e.g., `getUserById`).
84+
- Use **PascalCase** for class names and interfaces (e.g., `UserService`).
85+
- use **snake_case** for database columns and TypeORM entity properties (e.g., `first_name`, `last_name`) but use **camelCase** for variable names in TypeScript code.
86+
- Use suffixes like `.handler.ts`, `.command.ts`, `.query.ts`, `.event.ts`, `.subscriber.ts`, `.resolver.ts` for files in features (e.g, `create-user.command.ts`, `user.query.ts`).
87+
- Use suffixes like `Service`, `Repository`, `Controller` for class names (e.g., `UserService`, `UserRepository`, `UserController`).
88+
89+
#### Database Guidelines
90+
- Use **TypeORM** for database interactions.
91+
- Use **UUID v4** for unique identifiers, avoid auto-increment IDs.
92+
- Use repositories for data access in handlers, not direct TypeORM queries.
93+
- Always generate migrations using the provided npm script (`npm run generate-migration`), do not create them manually.
94+
- Never add parameters to the `npm run generate-migration` command, as it will fail.
95+
- All migrations are auto-executed at application start, do not run them manually.
96+
- Migrations are stored in `src/migrations/`.
97+
98+
#### Testing Guidelines
99+
- Every endpoint must have a corresponding integration test.
100+
- Every utility function must have a corresponding unit test.
101+
102+
#### Security Guidelines
103+
- Use `helmet` for security headers in Express.
104+
- Use `celebrate` for input validation in REST endpoints.
105+
- Use global error handling middleware for catching and formatting errors.
106+
- Add logging for important events and errors using injected logger in handlers and actions.
107+
- Never expose sensitive information in error messages or responses.
108+
- Use environment variables for sensitive configuration (e.g., database credentials, API keys) and load them using `dotenv`.
109+
- Use HTTPS for secure communication, especially in production environments.
110+
- Implement proper authentication and authorization mechanisms for protected routes.
111+
- Regularly update dependencies to patch security vulnerabilities.
112+
- Use `cors` middleware to control cross-origin requests, allowing only trusted origins.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
applyTo: "**/actions/*.action.ts"
3+
---
4+
5+
- Every action must have a corresponding integration test
6+
- Always use `@tshio/command-bus` for command handlers and `@tshio/query-bus` for query handlers.
7+
- GET methods should be used for queries, and POST, PUT, DELETE methods for commands.
8+
- When naming actions files always use names descriptive of the action being performed, for example:
9+
- `create-user.action.ts` for creating a user
10+
- `delete-user.action.ts` for deleting a user
11+
- `get-users.action.ts` for retrieving users
12+
- Use appropriate HTTP status codes for different actions (e.g., 200 for success, 201 for created, 204 for no content).
13+
- For errors use appropriate HTTP status codes (e.g., 400 for bad request, 404 for not found, 500 for internal server error).:
14+
- All errors must return a structured JSON response with an error message and code:
15+
```json
16+
{
17+
"error": {
18+
"message": "Error message",
19+
"code": "ERROR_CODE"
20+
}
21+
}
22+
```
23+
- Validation errors are handled by `celebrate` in `src/middleware/error-handler.ts` don't change that
24+
25+
- Use `GET` method for list endpoints.
26+
- Use `src/shared/pagination-utils.ts` for creating TypeORM options for list endpoints.
27+
- Always return a JSON response with `meta` and `data` fields.
28+
- The `meta` field should contain pagination, filter, sort, and search information.
29+
- Example request for list endpoint with pagination, sorting, filtering, and search - `GET /users?page=1&limit=10&sort[firstName]=ASC&filter[lastName]=Nowak&search=test`
30+
- Example response for a list endpoint:
31+
```json
32+
{
33+
"meta": {
34+
"pagination": {
35+
"page": 1,
36+
"total": 0,
37+
"limit": 10,
38+
"totalPages": 0
39+
},
40+
"filter": {
41+
"firstName": ["Ewa", "Adam"],
42+
"lastName": "Nowak"
43+
},
44+
"sort": {
45+
"firstName": "ASC"
46+
},
47+
"search": "test"
48+
},
49+
"data": [{ ... }]
50+
}
51+
```
52+
- Use `filter[field]=value` for filtering by field with specific value.
53+
- Filters on the same field are interpreted as OR: `filter[field1]=value1&filter[field1]=value2`
54+
- Filters on different fields are interpreted as AND: `filter[field1]=value1&filter[field2]=value2`
55+
- Using `%` at the end of value will be interpreted as LIKE query: `filter[field]=value%`
56+
- Use `sort[field]=ASC|DESC` for sorting by field in specified order.
57+
- Use `search=term` for general text search.
58+
- Use `page=1&limit=10` for pagination.
59+
- Use `GET` method for single item endpoints.
60+
- Use singular nouns for single item endpoint paths (e.g., `/users/123e4567-e89b-12d3-a456-426614174000`).
61+
- Always return a JSON response with the item data.
62+
- Example request for single item endpoint - `GET /users/123e4567-e89b-12d3-a456-426614174000`
63+
- Example response for a single item endpoint:
64+
```json
65+
{
66+
"id": "123e4567-e89b-12d3-a456-426614174000",
67+
"firstName": "Ewa",
68+
"lastName": "Nowak",
69+
"email": "ewa.nowak@example.com"
70+
}
71+
```
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
applyTo: "**/*.handler.ts"
3+
---
4+
5+
- All handlers must have a `.handler.ts` suffix.
6+
- Always use single parameter in constructor for dependencies. For example:
7+
```typescript
8+
import { UserService } from "../services/user.service";
9+
10+
interface UserHandlerDependencies {
11+
userService: UserService;
12+
}
13+
14+
export class UserHandler {
15+
constructor(private dependencies: UserHandlerDependencies) {}
16+
17+
async handle(request: Request, response: Response): Promise<void> {
18+
const user = await this.dependencies.userService.getUserById(request.params.id);
19+
response.json(user);
20+
}
21+
}
22+
```
23+
- Never use Command Bus or Query Bus directly in handlers. Instead, dispatch events using the provider `@tshio/event-dispatcher`
24+
- Always inject repositories and not data sources directly into handlers:
25+
```typescript
26+
import { UserEntity } from "../models/user.entity";
27+
import { Repository } from "typeorm";
28+
29+
interface UserHandlerDependencies {
30+
userRepository: Repository<UserEntity>;
31+
}
32+
33+
export class UserHandler {
34+
constructor(private dependencies: UserHandlerDependencies) {}
35+
36+
async handle(request: Request, response: Response): Promise<void> {
37+
const user = await this.dependencies.userRepository.findOneBy({ id: request.params.id });
38+
response.json(user);
39+
}
40+
}
41+
```
42+
- Never create tests for handlers.
43+
- Don't use `@CacheQuery` decorator in any handlers unless explicitly stated otherwise.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
applyTo: "**/+(*.entity.ts|*.repository.ts)"
3+
---
4+
5+
- All entities must have a `.entity.ts` suffix.
6+
- Always register repository for each entity in `src/container/database.ts`.
7+
- When registering a repository, use this approach:
8+
```typescript
9+
userRepository: asValue(dbDataSource.getRepository(UserEntity)),
10+
```
11+
- Use **UUID v4** for unique identifiers, avoid auto-increment IDs.
12+
- Use `@PrimaryColumn()` for UUID columns, not `@PrimaryGeneratedColumn()`.
13+
- Always generate migrations using the provided npm script (`npm run generate-migration`), do not create them manually.
14+
- Never add parameters to the `npm run generate-migration` command, as it will fail.
15+
- Never create tests for entities.
16+
- Always validate and sanitize user input to prevent SQL injection and other attacks.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
applyTo: "**/features/**/routing.ts"
3+
---
4+
5+
- Register routing in `src/app/router.ts`
6+
- When registering routing in `src/app/router.ts`, always register it with specific path, for example:
7+
```typescript
8+
router.use("/example", usersRouting);
9+
```
10+
- For list endpoints, use `GET` method and `/` format for path
11+
- For single item endpoints, use `GET` method and `/:id` format for path
12+
- For creating items, use `POST` method and `/` format for path
13+
- For updating items, use `PUT` method and `/:id` format for path
14+
- For deleting items, use `DELETE` method and `/:id` format for path
15+
- A good example of routing:
16+
```typescript
17+
export const exampleRouting = (actions: ExampleRoutingDependencies) => {
18+
const router = express.Router();
19+
20+
router.post(
21+
"/",
22+
[createExampleActionValidation],
23+
actions.createExampleAction.invoke.bind(actions.createExampleAction),
24+
);
25+
router.get(
26+
"/",
27+
[examplesActionValidation],
28+
actions.examplesAction.invoke.bind(actions.examplesAction),
29+
);
30+
router.get(
31+
"/:id",
32+
[exampleActionValidation],
33+
actions.exampleAction.invoke.bind(actions.exampleAction),
34+
);
35+
router.delete(
36+
"/:id",
37+
[deleteExampleActionValidation],
38+
actions.deleteExampleAction.invoke.bind(actions.deleteExampleAction),
39+
);
40+
// ACTIONS_SETUP
41+
42+
return router;
43+
};
44+
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
applyTo: "**"
3+
---
4+
5+
- When implmenting authentication, use JWT tokens for stateless authentication.
6+
- Never store sensitive information (like passwords) in plain text, always hash them using a strong hashing algorithm (e.g., bcrypt).
7+
- Always implement authentication with proper session management, including token expiration and refresh mechanisms.
8+
- The authentication middleware should validate the JWT token and extract user information from it.
9+
- Use role-based access control (RBAC) to manage permissions and restrict access to resources based on user roles.
10+
- Always validate user input to prevent common security vulnerabilities such as SQL injection, XSS, and CSRF.
11+
- Authentication endpoints should return both access and refresh tokens upon successful login:
12+
```json
13+
{
14+
"access_token": "your_access_token",
15+
"refresh_token": "your_refresh_token"
16+
}
17+
```
18+
- Always implement `me` endpoint to allow users to retrieve their own profile information.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
applyTo: "**/tests/*.spec.ts"
3+
---
4+
5+
- All tests must have a `.spec.ts` suffix.
6+
- Unit tests should be placed next to the file they test
7+
- Integration tests must be placed in `src/tests` directory
8+
- Tests for each feature must be placed in a separate file, named after the feature (e.g., `users.spec.ts`, `warehouses.spec.ts`).
9+
- Tests for each feature must be placed in a separate directory, named after the feature (e.g., `src/tests/users`, `src/tests/warehouses`).
10+
- Use following pattern for integration tests:
11+
```typescript
12+
import { expect } from "chai";
13+
import "mocha";
14+
import request from "supertest";
15+
16+
describe("/api/{{kebabCase name}} integration", () => {
17+
it("test example", async () => {
18+
return request(await global.container.cradle.app)
19+
.get("/health")
20+
.expect(200)
21+
});
22+
});
23+
```
24+
- Test bootstrap is in `src/tests/bootstrap.ts`, which sets up the container, database connection, and clears the database before each test.
25+
- Use `clearDb()` function in `src/tests/bootstrap.ts` to clear the database before each test.
26+
- Every repository must be added to `clearDb()` function and cleared with `.delete({})` to avoid TypeORM issues.
27+
- Use `global.container` and `global.dbConnection` in tests for accessing the container and database connection.
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { MigrationInterface, QueryRunner } from "typeorm";
22

33
export class AddUserEntity1710240086737 implements MigrationInterface {
4-
name = 'AddUserEntity1710240086737'
4+
name = "AddUserEntity1710240086737";
55

6-
public async up(queryRunner: QueryRunner): Promise<void> {
7-
await queryRunner.query(`CREATE TABLE "user" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "first_name" character varying NOT NULL, "last_name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
8-
await queryRunner.query(`INSERT INTO "user" (first_name, last_name, email) VALUES ('John', 'Doe', 'john@doe.com'), ('Mark', 'Smith', 'mark@smith.com')`);
9-
}
10-
11-
public async down(queryRunner: QueryRunner): Promise<void> {
12-
await queryRunner.query(`DROP TABLE "user"`);
13-
}
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(
8+
`CREATE TABLE "user" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "first_name" character varying NOT NULL, "last_name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`,
9+
);
10+
await queryRunner.query(
11+
`INSERT INTO "user" (first_name, last_name, email) VALUES ('John', 'Doe', 'john@doe.com'), ('Mark', 'Smith', 'mark@smith.com')`,
12+
);
13+
}
1414

15+
public async down(queryRunner: QueryRunner): Promise<void> {
16+
await queryRunner.query(`DROP TABLE "user"`);
17+
}
1518
}

0 commit comments

Comments
 (0)