Loading repository data…
Loading repository data…
shawon-kanji / repository
A production-ready TypeScript Node.js boilerplate implementing Clean Architecture with proper dependency inversion, DDD principles, and multi-database support (MySQL + MongoDB). Features Abstract Factory pattern, bounded contexts, and zero framework dependencies for DI.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
A professional TypeScript Node.js boilerplate implementing Clean Architecture (Hexagonal Architecture), Domain-Driven Design, and multi-database support with proper dependency inversion.
# Install dependencies
yarn install
# Configure environment
cp .env.example .env
# Edit .env with your database credentials
# Run development server
yarn dev
# Build for production
yarn build
yarn start
┌─────────────────────────────────────────────────────┐
│ Presentation Layer (Routes, Controllers) │
│ Composition Root │
└──────────────────────┬──────────────────────────────┘
│ depends on
┌──────────────────────▼──────────────────────────────┐
│ Application Layer (Services, Use Cases) │
│ Defines IRepositoryFactory │
└──────────────────────┬──────────────────────────────┘
│ depends on
┌──────────────────────▼──────────────────────────────┐
│ Domain Layer (Entities, Value Objects) │
│ Repository Interfaces (Ports) │
└──────────────────────────────────────────────────────┘
▲
│ implements
┌──────────────────────┴──────────────────────────────┐
│ Infrastructure Layer (Implementations) │
│ Repository Implementations, RepositoryFactory │
└──────────────────────────────────────────────────────┘
Key Principle: All dependencies point inward. The Application layer defines interfaces; Infrastructure implements them.
src/
├── domain/ # Business logic (zero dependencies)
│ ├── entities/ # User
│ ├── value-objects/ # Email, UserId
│ └── repositories/ # IUserRepository (interface)
│
├── application/ # Business use cases
│ ├── dto/ # Data Transfer Objects
│ ├── interfaces/ # IRepositoryFactory
│ ├── services/ # UserManagementService, UserProfileService
│ └── use-cases/ # CreateUserUseCase, GetUserByIdUseCase, etc.
│
├── infrastructure/ # Technical implementations
│ ├── config/
│ │ ├── repositories.ts # Singleton factory functions
│ │ ├── RepositoryFactory.ts # Implements IRepositoryFactory
│ │ └── serviceFactory.ts # Service composition functions
│ ├── database/ # MySQL and MongoDB connections
│ └── persistence/
│ ├── mysql/ # MySQLUserRepository
│ └── mongodb/ # MongoDBUserRepository
│
└── presentation/ # HTTP interface
├── controllers/ # UserController
└── routes/ # Composition root
Dependencies are wired together in the composition root (route files):
// src/presentation/routes/userRoutes.ts
import { createUserProfileService, createUserManagementService }
from '../../infrastructure/config/serviceFactory';
// 1. Create services using factory functions
const profileService = createUserProfileService();
const managementService = createUserManagementService();
// 2. Inject into controller
const userController = new UserController(profileService, managementService);
// 3. Define routes
router.post('/', (req, res) => userController.createUser(req, res));
The Application layer defines the factory interface:
// src/application/interfaces/IRepositoryFactory.ts
export interface IRepositoryFactory {
createMySQLUserRepository(): IUserRepository;
createMongoDBUserRepository(): IUserRepository;
}
Infrastructure implements the interface:
// src/infrastructure/config/RepositoryFactory.ts
export class RepositoryFactory implements IRepositoryFactory {
createMySQLUserRepository(): IUserRepository {
return getMySQLUserRepository(); // Singleton
}
createMongoDBUserRepository(): IUserRepository {
return getMongoDBUserRepository(); // Singleton
}
}
Services receive the factory via constructor:
// src/application/services/UserManagementService.ts
export class UserManagementService {
constructor(private repositoryFactory: IRepositoryFactory) {}
async createUser(data: CreateUserDTO) {
// Service creates use case with repositories from factory
const mysqlRepo = this.repositoryFactory.createMySQLUserRepository();
const mongoRepo = this.repositoryFactory.createMongoDBUserRepository();
const useCase = new CreateUserUseCase(mysqlRepo, mongoRepo);
return useCase.execute(data);
}
}
Benefits:
Services represent bounded contexts:
Each service groups related use cases by business capability.
Use cases implement single business operations:
export class CreateUserUseCase {
constructor(
private mysqlRepository: IUserRepository,
private mongoRepository: IUserRepository
) {}
async execute(data: CreateUserDTO): Promise<UserDTO> {
const user = User.create(data);
// Dual-write pattern
await this.mongoRepository.save(user);
await this.mysqlRepository.save(user);
return this.mapToDTO(user);
}
}
Data is written to both MySQL and MongoDB simultaneously:
Both databases are required:
# MySQL
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_DATABASE=hexacore
# MongoDB
MONGODB_URI=mongodb://localhost:27017/hexacore
# Create user
POST /api/users
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
# Get all users
GET /api/users
# Get user by ID
GET /api/users/:id
# Update user
PUT /api/users/:id
{
"name": "Jane Doe",
"email": "jane@example.com"
}
# Delete user
DELETE /api/users/:id
# Health check
GET /health
Entities encapsulate business logic:
export class User {
private constructor(
private readonly id: UserId,
private name: string,
private email: Email,
private readonly age?: number
) {}
static create(props: CreateUserProps): User {
// Validation and business rules
return new User(UserId.generate(), props.name, new Email(props.email), props.age);
}
updateEmail(newEmail: string): void {
this.email = new Email(newEmail); // Validates
}
}
Value objects ensure domain invariants:
export class Email {
constructor(private readonly value: string) {
if (!this.isValid(value)) {
throw new Error('Invalid email format');
}
}
private isValid(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
src/application/use-cases/src/application/services/IRepositoryFactorysrc/infrastructure/config/serviceFactory.tssrc/domain/entities/src/domain/repositories/src/infrastructure/persistence/IRepositoryFactory and RepositoryFactoryconst mockMySQLRepo = createMock<IUserRepository>();
const mockMongoRepo = createMock<IUserRepository>();
const useCase = new CreateUserUseCase(mockMySQLRepo, mockMongoRepo);
await useCase.execute(userData);
expect(mockMySQLRepo.save).toHaveBeenCalled();
expect(mockMongoRepo.save).toHaveBeenCalled();
const mockFactory: IRepositoryFactory = {
createMySQLUserRepository: () => mockMySQLRepo,
createMongoDBUserRepository: () => mockMongoRepo,
};
const service = new UserManagementService(mockFactory);
await service.createUser(userData);
test('Email validates format', () => {
expect(() => new Email('invalid')).toThrow('Invalid email format');
expect(() => new Email('valid@example.com')).not.toThrow();
});
yarn dev # Development with hot reload
yarn build # Build TypeScript to JavaScript
yarn start # Production: build + run
yarn lint # Run ESLint
yarn format # Format with Prettier
✅ Testability - Easy to mock dependencies ✅ Maintainability - Clear separation of concerns ✅ Flexibility - Swap implementations without changing business logic ✅ Independence - Business logic independent of frameworks and databases ✅ Scalability - Add features without affecting existing code ✅ Type Safety - Full TypeScript support
ARCHITECTURE.md - Detailed architecture documentationCLAUDE.md - Guide for Claude Code AI assistantMIT
Created by Shawon Kanji
Built with Clean Architecture and Domain-Driven Design principles for maximum flexibility and maintainability.