0.0.2 β€’ Published 9 months ago

@churro/core v0.0.2

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
9 months ago

@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-metadata

Make 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! πŸš€