clean-architecture-ts

Clean Architecture for Remix/TypeScript Apps

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "clean-architecture-ts" with this command: npx skills add toilahuongg/shopify-agents-kit/toilahuongg-shopify-agents-kit-clean-architecture-ts

Clean Architecture for Remix/TypeScript Apps

As Remix apps grow, loader and action functions can become bloated "God Functions". This skill emphasizes separation of concerns.

  1. The Layers

A. The Web Layer (Loaders/Actions)

Responsibility: Parsing requests, input validation (Zod), and returning Responses (JSON/Redirect). Rule: NO business logic here. Only orchestration.

// app/routes/app.products.update.ts export const action = async ({ request }: ActionFunctionArgs) => { const { shop } = await authenticate.admin(request); const formData = await request.formData();

// 1. Validate Input const input = validateProductUpdate(formData);

// 2. Call Service const updatedProduct = await ProductService.updateProduct(shop, input);

// 3. Return Response return json({ product: updatedProduct }); };

B. The Service Layer (Business Logic)

Responsibility: The "What". Rules, calculations, error handling, complex flows. Rule: Framework agnostic. Should not know about "Request" or "Response" objects.

// app/services/product.service.ts export class ProductService { static async updateProduct(shop: string, input: ProductUpdateInput) { // Business Rule: Can't update archived products const existing = await ProductRepository.findByShopAndId(shop, input.id); if (existing.status === 'ARCHIVED') { throw new BusinessError("Cannot update archived product"); }

// Business Logic
const result = await ProductRepository.save({
  ...existing,
  ...input,
  updatedAt: new Date()
});

return result;

} }

C. The Repository Layer (Data Access)

Responsibility: The "How". interaction with Database (Prisma), APIs (Shopify Admin), or File System. Rule: Only this layer touches the DB/API.

// app/repositories/product.repository.ts export class ProductRepository { static async findByShopAndId(shop: string, id: string) { return prisma.product.findFirstOrThrow({ where: { shop, id: BigInt(id) } }); } }

  1. Directory Structure

app/ routes/ # Web Layer services/ # Business Logic repositories/ # Data Access (DB/API) models/ # Domain Types / Interfaces utils/ # Pure functions (math, string manipulation)

  1. Dependency Injection (Optional but Recommended)

For complex apps, use a container like tsyringe to manage dependencies, especially for testing (mocking Repositories).

// app/services/order.service.ts @injectable() export class OrderService { constructor( @inject(OrderRepository) private orderRepo: OrderRepository, @inject(ShopifyClient) private shopify: ShopifyClient ) {} }

  1. Error Handling

Create custom Error classes to differentiate between "Bad Request" (User error) and "Server Error" (System error).

// app/errors/index.ts export class BusinessError extends Error { public code = 422; }

export class NotFoundError extends Error { public code = 404; }

Refactor your loader /action to catch these errors and return appropriate HTTP status codes.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript

No summary provided by upstream source.

Repository SourceNeeds Review
General

shopify-liquid

No summary provided by upstream source.

Repository SourceNeeds Review
General

shopify-polaris-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

docusaurus-generator

No summary provided by upstream source.

Repository SourceNeeds Review