Enhancing React Frontend Architecture with Domain-Driven Design and TypeScript Using Neverthrow
Elevate your React applications with Domain-Driven Design (DDD)! This engaging guide explains how to integrate DDD using TypeScript and Neverthrow for better error handling. Dive into our practical use case with detailed code examples and folder organization.
Domain-Driven Design (DDD) offers a strategic approach to developing complex software systems, emphasizing a deep focus on the core domain and its logic. When applied to frontend development, particularly with React and TypeScript, DDD facilitates a well-organized codebase that is easy to manage and scales gracefully. By using TypeScript along with Neverthrow, a library for handling errors in a functional way, developers can further enhance the robustness and maintainability of their applications.
Use Case: Online E-commerce Store
In this article, we'll implement DDD in a React project for an online e-commerce store. This store includes features like product listings, a shopping cart, user profiles, and order management.
Folder Structure
A clear folder structure helps separate the domain logic from application logic, adhering to DDD principles:
/src
/domain
/product
product.ts
productService.ts
/cart
cart.ts
cartService.ts
/user
user.ts
userService.ts
/infrastructure
/api
productApi.ts
userApi.ts
cartApi.ts
/ui
/components
ProductList.tsx
Cart.tsx
UserProfile.tsx
/pages
HomePage.tsx
ProductPage.tsx
CartPage.tsx
Domain Model Example
Below is a TypeScript example of a domain model for a product:
// src/domain/product/product.ts
import { err, ok, Result } from 'neverthrow';
export interface Product {
id: string;
name: string;
price: number;
description: string;
imageUrl: string;
}
export class Product {
constructor(
public id: string,
public name: string,
public price: number,
public description: string,
public imageUrl: string
) {}
applyDiscount(discountPercentage: number): Result<void, Error> {
if (discountPercentage <= 0 || discountPercentage > 100) {
return err(new Error('Invalid discount percentage'));
}
this.price = this.price * (1 - discountPercentage / 100);
return ok();
}
}
Services
Services manage the business logic related to domain entities. Here’s how a product service might look using Neverthrow for better error handling:
// src/domain/product/productService.ts
import { Product } from './product';
import { productApi } from '../../infrastructure/api/productApi';
import { Result } from 'neverthrow';
export class ProductService {
async getAllProducts(): Promise<Result<Product[], Error>> {
return await productApi.fetchProducts();
}
async getProductById(id: string): Promise<Result<Product, Error>> {
return await productApi.fetchProductById(id);
}
}
Product API with Neverthrow
Now, let's implement the productApi
with error handling using Neverthrow:
// src/infrastructure/api/productApi.ts
import { Product } from '../../domain/product/product';
import { err, ok, Result } from 'neverthrow';
const API_URL = 'https://api.yourdomain.com/products';
export const productApi = {
async fetchProducts(): Promise<Result<Product[], Error>> {
try {
const response = await fetch(`${API_URL}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return err(new Error('Failed to fetch products'));
}
const products: Product[] = await response.json();
return ok(products);
} catch (error) {
return err(new Error('Network error occurred while fetching products'));
}
},
async fetchProductById(id: string): Promise<Result<Product, Error>> {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return err(new Error(`Failed to fetch product with id ${id}`));
}
const product: Product = await response.json();
return ok(product);
} catch (error) {
return err(new Error(`Network error occurred while fetching product with id ${id}`));
}
}
};
Conclusion
Incorporating Domain-Driven Design into your React applications using TypeScript and Neverthrow can significantly improve the structure, scalability, and error handling of your codebase. By aligning your software development with business needs,
DDD helps deliver solutions that are not only technically sound but also strategically focused. Start using these principles and tools to build robust and business-oriented frontend applications.
Happy coding!