Clean Architecture is a popular software development design pattern that aims to separate concerns and create a modular structure for applications. In this tutorial, we will learn how to implement Clean Architecture in a Next.js application.
Next.js is a popular React framework for building server-side rendered applications. By implementing Clean Architecture in a Next.js application, we can create a scalable, maintainable, and testable codebase.
Let’s start by understanding the principles of Clean Architecture:
-
Separation of Concerns: Clean Architecture promotes the separation of concerns by dividing the application into distinct layers such as domain, application, and infrastructure.
-
Dependency Rule: The inner layers should not depend on the outer layers. This means that the business rules defined in the domain layer should not be directly dependent on the framework or infrastructure layer.
- Testability: Clean Architecture emphasizes writing tests at each layer of the application. This ensures that changes can be made to one layer without affecting the others.
Now, let’s implement Clean Architecture in a Next.js application:
- Create a new Next.js project:
First, create a new Next.js project using the following command:
npx create-next-app@latest my-next-app
- Define the folder structure:
Next, define the folder structure for the Clean Architecture in your Next.js application. Here is a recommended folder structure:
- components/
- pages/
- domain/
- entities/
- repositories/
- useCases/
- application/
- services/
- infrastructure/
- dataSources/
- repositories/
- gateways/
-
Implement the domain layer:
The domain layer contains the core business logic of the application. Define entities, repositories, and use cases in the domain layer. Entities represent the core models of the application, repositories define the data access layer, and use cases define the business logic. -
Implement the application layer:
The application layer contains the services that interact with the domain layer. Define services that orchestrate the use cases defined in the domain layer. -
Implement the infrastructure layer:
The infrastructure layer contains the implementations that interact with external systems such as databases, APIs, etc. Define data sources, repositories, and gateways in the infrastructure layer. -
Connect the layers:
Connect the layers by injecting dependencies from the outer layers to the inner layers. Use inversion of control containers or dependency injection frameworks to manage the dependencies. - Write tests:
Write tests for each layer of the application to ensure that the code is working as expected. Use tools such as Jest for unit testing and Cypress for end-to-end testing.
By implementing Clean Architecture in a Next.js application, you can create a well-structured, maintainable, and testable codebase. Remember to follow the separation of concerns and dependency rule principles to create a clean and modular architecture.
This is the definitive guide on how to implement Clean Architecture in Next.js. It's almost an hour, so grab a coffee or tea and dive in. Hope you enjoy this video! Some of you pointed out that using Sentry directly is breaking CA’s rules, and you’re right! I had to take a shortcut there, but I forgot to mention that in the video. If you have any questions, don't hesitate to reach out to me in the comments, or in my Discord server: https://creatures.sh.
🔗 Links from the video:
👉 GitHub Repo: https://github.com/nikolovlazar/nextjs-clean-architecture
👉 Demo: https://next-clean-arch.vercel.app/
👉 Clean Architecture article: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
👉 My interpretation diagram: https://app.eraser.io/workspace/fAyjQlkBiC7AoAoYoPnw?origin=share
👉 Configure Inversify in Next.js video: https://youtu.be/2NVYG5VDmwQ
👉 Test case naming guide: https://www.epicweb.dev/talks/how-to-write-better-test-names
what theme is this in nvim?
Bro, you saved my life! I'm struggled importing the reflect-metadata on NextJS all the time to implement dependency injection!
Изключително добре направено! Браво!
Great content, thank you! I have a question regarding the sentry config included in your project. I have never used sentry, suppose i would like to do a code along with your tutorial, will a free sentry account be sufficient to apply the sentry features or is a paid subscription with them required ? Thanks!
Hey, this is super interesting… however what I'm struggling with, is that in this approach frontend and backend is completely separated, which I get is the idea of this paradigm, however in practice the lines are not always clear. For example lets say I build a tax return form: I might want all the business logic structured as presented to nicely handle a submitted form on the server side but still show the user an estimated value while they are filling the form which doesn't need (and I think shouldn't) be calculated in the backend for every changed value. Same with validation: If I'm already using zod and frontend libraries, I don't have to deal with validation and displaying errors… this is taken care of in the frontend. I still need to perform the same checks on the backend but ideally a non-malicious user only submits validated data once everything is in order. A model might be fine to import from the frontend and use as my validation scheme but the calculator example its not quite clear to me how I would deal with such a scenario and where I would put the code/helper methods which perform the calculations in the end in order to maintain separation and allow reusability without risking to import code that is only meant for the server side (which might e.g. leak secrets) on the client side.
Any help/ideas would be greatly apreciated!
Top!
Okay, nice explanation, looks great, only one thing bothers me, and it is a big big thing for me.. an _all_ layers you have the sentry calls.. on ALL layers. How does this fit into the clean architecture, clean maintainable code etc. I don't see it, sorry. Otherwise great explanation.
Awesome bro, we absolutely need more of this excellent content. subscribed for more of this! thank you!
Thank you Lazar for your big efforts in this topic, your are using Lucia in this example, I'm struggling to do something similar with Next Auth, is there a good article/video about this approach?
this is what quality content look like. thanks!
QQ about input validation: Architecture wise ( looking good on a graph ), validation looks better in the controllers so both incoming ( from server ) and outgoing ( to server ) data is validated in the same place. But implementation wise validating your input data makes much more sense in the context of the form. Is the validation and form linked only by the form libraries, so its just getting used to it, or the proximity of the input to the form warrants its. Like you can validate the data without ever leaving the context of the form and calling a controller, vs calling the controller and returning the validation error in your onSubmit function and setting the errors explicitly from there ( as most form libs handle this explicitly ) ?
Thank you for the incredible amount of high quality information that you have packed into a hour long video! Very much appreciated 🙏 One question I would really like your opinion on and that is the choice for a DI library in TypeScript. You chose InversifyJS but there are others like TypeDI and tsyringe. All of them are not actively maintained for the past couple of years. Do you see any risks of implementing DI with these libraries, as is it part of the core of your codebase?
Would love to see this applied for a React + Vite SPA application!
Can we hire you?
Thanks for creating it. It blows my mind. Need such content more on your channel.
Thank you very much for the enormous effort you put into making this tutorial. I really like how you configure vscode. Can you share it with us? THANKS
Fantastic video. I’ve been searching for a pattern to help me apply domain driven design properly to next JS projects and this example is the inspiration I needed. So comprehensive!
Hey mate, question on your errors entities you have a type called ErrorOptions but its not being defined anywhere in the code and i dont see it as imported from an external package, where does that come from?
Thanks!
Thanks for this video @nikolovlazar !
In your codebase (25:20) you talk about user authorization in use-case. So if we want to get more infos on user (free / paid on your example), we should retrieve the user repository and get user in this same use-case right ?