0.0.2 β’ Published 9 months ago
@churro/core v0.0.2
@churros/core - A DDD & Clean Architecture Framework for TypeScript
π Why This Framework?
Modern backend development often struggles with tight coupling, lack of structure, and scalability issues. @churros/core provides an opinionated yet flexible Domain-Driven Design (DDD) and Clean Architecture framework, ensuring business logic is at the center while keeping infrastructure decoupled.
The Philosophy: Freedom with Guidance
- β Freedom β You can use any ORM, API framework, or messaging system.
- β Guidance β Enforces DDD best practices like Entities, Aggregates, Repositories, Use Cases, CQRS, and Domain Events.
- β Flexibility β Works with Fastify, Express, GraphQL, or gRPC.
- β Scalability β Supports microservices, monoliths, event-driven architectures.
π― Core Principles
- π§ Domain-Driven β Everything revolves around business logic.
- π Decoupled Infrastructure β The domain knows nothing about persistence, API layers, or external services.
- π¦ Modular & Scalable β Supports CQRS, Event-Driven Systems, Microservices.
- π Framework-Agnostic β Use Fastify, Express, NestJS, or GraphQL.
- π Dependency Injection (DI) β Uses InversifyJS for flexibility and testability.
π Project Structure
/your-app
βββ src/
β βββ core/ # Framework Core (Entities, Aggregates, Repositories, Use Cases, etc.)
β βββ domain/ # Business logic (Entities, Repositories, Domain Services, Events)
β βββ application/ # Use Cases, Command/Query Handlers
β βββ infrastructure/ # Persistence, Messaging, API Controllers
β βββ interface/ # Fastify/GraphQL API Layer
β βββ server.ts # Application entry point
βββ package.json
βββ tsconfig.json
βββ README.mdπ Installation
npm install @churros/core inversify reflect-metadataMake sure reflect-metadata is enabled in your tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}π Usage
1οΈβ£ Defining an Entity
import { Entity } from "@churros/core";
export class Product extends Entity<{ name: string; price: number }> {
constructor(props: { name: string; price: number }, id?: string) {
super(props, id);
}
updatePrice(newPrice: number) {
this.price = newPrice;
}
}2οΈβ£ Implementing a Repository
import { Repository } from "@churros/core";
import { Product } from "../entities/Product";
export abstract class ProductRepository implements Repository<Product> {
abstract findById(id: string): Promise<Product | null>;
abstract save(product: Product): Promise<void>;
abstract delete(id: string): Promise<void>;
}3οΈβ£ Creating an In-Memory Repository
import { injectable } from "inversify";
import { ProductRepository } from "../../domain/repositories/ProductRepository";
import { Product } from "../../domain/entities/Product";
@injectable()
export class InMemoryProductRepository implements ProductRepository {
private products: Map<string, Product> = new Map();
async findById(id: string): Promise<Product | null> {
return this.products.get(id) || null;
}
async save(product: Product): Promise<void> {
this.products.set(product.id, product);
}
async delete(id: string): Promise<void> {
this.products.delete(id);
}
}4οΈβ£ Implementing a Use Case
import { injectable, inject } from "inversify";
import { UseCase } from "@churros/core";
import { Product } from "../../domain/entities/Product";
import { ProductRepository } from "../../domain/repositories/ProductRepository";
import { TYPES } from "../../core/container";
@injectable()
export class CreateProduct extends UseCase<{ name: string; price: number }, Product> {
constructor(@inject(TYPES.ProductRepository) private productRepo: ProductRepository) {
super();
}
async execute(input: { name: string; price: number }): Promise<Product> {
const product = new Product(input);
await this.productRepo.save(product);
return product;
}
}5οΈβ£ Registering Dependencies with InversifyJS
import "reflect-metadata";
import { Container } from "inversify";
import { ProductRepository } from "../domain/repositories/ProductRepository";
import { InMemoryProductRepository } from "../infrastructure/persistence/InMemoryProductRepository";
import { CreateProduct } from "../application/use-cases/CreateProduct";
const TYPES = {
ProductRepository: Symbol.for("ProductRepository"),
CreateProduct: Symbol.for("CreateProduct"),
};
const container = new Container();
container.bind<ProductRepository>(TYPES.ProductRepository).to(InMemoryProductRepository);
container.bind<CreateProduct>(TYPES.CreateProduct).to(CreateProduct);
export { container, TYPES };6οΈβ£ Exposing an API with Fastify
import Fastify from "fastify";
import { container, TYPES } from "./core/container";
import { CreateProduct } from "./application/use-cases/CreateProduct";
const fastify = Fastify({ logger: true });
fastify.post("/products", async (request, reply) => {
const createProduct = container.get<CreateProduct>(TYPES.CreateProduct);
const product = await createProduct.execute(request.body);
return reply.send(product);
});
fastify.listen({ port: 3000 }, () => {
console.log("π Server running on http://localhost:3000");
});π Final Thoughts
@churros/core empowers developers by enforcing best practices while offering the flexibility to build scalable applications.
π₯ Whatβs Next?
- Event Bus & Domain Events π¨
- CQRS & Command Bus β‘
- Database Integration (Prisma, TypeORM, MongoDB) ποΈ
- Authentication & Authorization (RBAC, OAuth, JWT) π
π€ Have feedback? Want a feature? Open an issue or contribute! π