React + Clean Architecture
⏳ 5 min
Approach to Clean Architecture with React and Typescript.
This time I want to show you an approach to what a clean architecture would look like using React + Typescript. I say approach because obviously, there’s always room for improvement.
First of all, get comfortable, grab yourself a cup of ☕️, and let’s dive in.
The first point will be how we’re going to organize our folders, giving us an overview of how the project will be structured.
proyecto/
├── src/
│ ├── Domain/
│ ├── Application/
│ ├── Infrastructure/
│ ├── Presentation/
Perfect, now that we have a general overview of the project structure, let’s dive into each of these ‘layers’.
Domain
This is the heart of our application. Here is where we define our business entities, which are like the main characters of our story. For example, if we were building a blog application, we could have a Post
entity representing a blog entry itself. Additionally, this is where our repositories
reside, which are responsible for communicating with our database or any other external data source.
Application
This layer is where the magic happens. Here we define our use cases, which are like the adventures that our characters (the entities) are going to experience. Continuing with our example of the blog application, we could have a use case called getPosts
responsible for the entire process of fetching the list of blog entries; another example would be createPost
, which handles the entire process of creating a new entry. This layer acts as the conductor of the orchestra, coordinating how our entities and repositories are used to achieve the application’s objectives. Ultimately, it’s where the business logic resides.
Infrastructure
This is where everything connects with the outside world. We have our Data Transfer Objects (DTOs), which are like messengers carrying information from one place to another, especially useful when working with web services or APIs. We also find our concrete implementations of repositories, where we define how to interact with our actual data sources, whether it’s a database, a REST API, or anything else. And last but not least, here we can also find utilities for handling things like HTTP requests.
Presentation
This is the layer where the magic of the user experience (UX) comes to life. It’s where our user interfaces (UI) take shape. Here is where we define our React components (or any other framework we’re using) and our pages.
With these four well-defined and organized layers, we have a solid foundation for building a robust, maintainable, and scalable application.
Let’s dive into action!
At this point… I think it’s time to see how all this theory translates into real code!
Let’s do it!
Domain layer
// Domain/entities/Post.ts
export interface Post {
id: number
title: string
body: string
}
// Domain/repositories/postRepository.ts
export interface PostRepository {
getPosts: () => Promise<Post[]>
// ...resto de métodos
}
Application layer
// Application/useCases/getPostUseCase.ts
export class GetPostsUseCase {
postRepository: PostRepositoryImpl
constructor() {
this.postRepository = new PostRepositoryImpl()
}
execute() {
return this.postRepository.getPosts()
}
}
Infrastructure layer
// Infrastructure/DTOs/PostDTO.ts
export interface PostDTO {
id: number
title: string
body: string
userId: number
}
At this point, I’ll show you an implementation of a utility to handle HTTP requests:
// Infrastructure/http/index.ts
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
type Headers = {
[key: string]: string
}
type IHttp<TBody> = {
token?: string | null
url: string
method?: HttpMethod
headers?: Headers
body?: TBody
}
async function Http<TResponse, TBody>(options: IHttp<TBody>): Promise<TResponse> {
const {
token,
url,
method = "GET",
headers = { ContentType: "application/json" },
body,
} = options
const authToken = {
...(headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
}
const response = await fetch(url, {
method,
headers: { ...headers, ...authToken },
body: body ? JSON.stringify(body) : undefined,
})
return await response.json()
}
export default Http
And we continue with the implementation of the repository:
export class PostRepositoryImpl implements PostRepository {
async getPosts(): Promise<Post[]> {
const posts: PostDTO[] = await http({
url: "https://jsonplaceholder.typicode.com/posts",
method: "GET",
})
return posts.map((postDTO): Post => {
return {
id: postDTO.id,
title: postDTO.title,
body: postDTO.body,
}
})
}
}
Presentation layer
Let’s go with a couple of UI examples where we’ll display the list of entries:
A component:
// Presentation/components/PostItem.tsx
export default function PostItem({ id, title, body }: Post) {
return (
<li key={id}>
<h3>{title}</h3>
<p>{body}</p>
</li>
)
}
A page:
// Presentation/pages/PostsPage.ts
export default function DemoPage() {
const [posts, setPosts] = useState<Post[] | null>(null)
const handleGetPosts = async () => {
const postsService = new GetPostsUseCase()
const response = await postsService.execute()
setPosts(response)
}
useEffect(() => {
handleGetPosts()
}, [])
return (
<>
<ul>
{posts &&
posts.map((item) => {
return <PostItem id={item.id} title={item.title} body={item.body} />
})}
</ul>
</>
)
}
And that’s it!
🎉 We’ve taken a brief tour through the fundamentals of structuring an application with React and TypeScript using clean architecture. From organizing our folders to writing the code for each layer.
I hope you now have a better idea of how to keep your project well-organized and easy to maintain.
💪 Remember, a good structure not only makes your code cleaner and more manageable, but it also makes life easier for any developer working with you (or even yourself in the future!). So, next time you start a project, don’t underestimate the power of good architecture.