-
Notifications
You must be signed in to change notification settings - Fork 0
Add a new Feature
This guide outlines the step-by-step process for introducing a new feature into the codebase using a Test-Driven Development (TDD) approach. Follow this checklist to ensure consistency, scalability, and maintainability.
Before making any changes:
git checkout -b feature/[feature-name]Use clear and descriptive names.
Example: feature/auth-register
If your feature involves data persistence:
- Define new
@Entity()classes insrc/database/entities/ - Use appropriate decorators (
@Column,@ManyToOne, etc.) - Add entity relations, constraints, and indexes as needed
Create a new folder under src/logic/model:
mkdir src/logic/model/[feature]- Create a Joi schema
- Create a
VALIDATORobject withbody,params, and/orquery
Naming Conventions:
- Joi schema:
post[Feature][Action] - Validator object:
POST_[FEATURE]_[ACTION]
📄 File: src/logic/model/[feature]/[feature].schema.ts
export const postAuthRegister = () =>
Joi.object({
username: Joi.string()
.min(JOI_CONFIG.USER.MIN_USERNAME_LENGTH)
.max(50)
.required(),
password: Joi.string()
.min(JOI_CONFIG.USER.MIN_PASSWORD_LENGTH)
.max(50)
.pattern(new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_]).{8,}$'))
.required()
.messages({
'string.pattern.base':
'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
}),
email: Joi.string().email().required(),
}).options(JOI_CONFIG.DEFAULT_OPTIONS);
const POST_AUTH_REGISTER: SchemaMap = {
body: postAuthRegister(),
};
export default { POST_AUTH_REGISTER };📄 File: src/logic/model/[feature]/[feature].types.ts
import '@goodrequest/joi-type-extract/index.d.ts';
export type AuthRegisterRequest = Joi.extractType<
ReturnType<typeof postAuthRegister>
>;Also in *.types.ts, define:
- DTOs
- Response types
- Utility types
export interface AuthDTO {
id: number;
username: string;
email: string;
password?: string;
uuid: string;
createdAt: Date;
}
export type AuthUserResponse = Pick<
AuthDTO,
'username' | 'email' | 'createdAt' | 'uuid'
>;
export interface AuthResponse {
token: string;
refreshToken: string;
user: AuthUserResponse;
}Create interfaces for each layer:
// repository/[feature].repository.interface.ts
export interface IAuthRepository {
create(auth: AuthRegisterRequest): Promise<AuthDTO>;
}
// service/[feature].service.interface.ts
export interface IAuthService {
register(
auth: AuthRegisterRequest,
sessionInfo?: AuthSessionInfo,
): Promise<AuthResponse>;
}
// controller/[feature].controller.interface.ts
export interface IAuthController {
register(req: Request, res: Response, next: NextFunction): Promise<void>;
}📄 Update src/config/index.ts:
export const INJECTION_TOKENS = {
IAuthController: 'AuthController',
IAuthService: 'IAuthService',
IAuthRepository: 'IAuthRepository',
};📄 Then, in src/config/container.ts:
container.register(INJECTION_TOKENS.IAuthController, {
useClass: AuthController,
});
container.register(INJECTION_TOKENS.IAuthService, {
useClass: AuthService,
});
container.register(INJECTION_TOKENS.IAuthRepository, {
useClass: TypeormAuthRepository,
});Write code + unit and integration tests.
- 🧠 Service = Business logic
- 🗂️ Repository = DB logic
- 📡 Controller = HTTP logic
Examples:
-
auth.repository.ts&auth.repository.unit.test.ts -
auth.service.ts&auth.service.unit.test.ts -
auth.controller.ts&auth.int.test.ts
(See earlier content for example implementations.)
📄 File: src/routes/[feature].routes.ts
const rawController = container.resolve<IAuthController>(
INJECTION_TOKENS.IAuthController,
);
const authController = bindAll(rawController);
const router = Router();
router.post(
'/register',
validate(VALIDATOR.POST_AUTH_REGISTER),
loggingMiddleware,
authController.register,
);
export default router;📄 File: src/app.ts
const registerRoutes = async (): Promise<Router> => {
const router = Router();
const auth = (await import('@model/auth/auth.routes')).default;
router.use('/auth', auth);
return router;
};Before pushing your code, run:
npm run commit:check- This ensures your commits follow the conventional format and pass all hooks:
- npm run format
- npm run lint
- npm run test
- npm run build
Push your feature branch:
git add .
git commit -m "Descriptive commit message"
git push origin feature/[feature-name]Then:
- Open your Git platform (e.g., GitHub/GitLab)
- Create a Pull Request (PR) to
main - Fill out the PR template (if available)
- Request code review
Your feature is now:
- ✅ Fully implemented
- ✅ Tested
- ✅ Integrated into architecture
- ✅ Ready for code review and deployment