Update: I refactored the app and now we're running on any runtime, including Vercel's Edge (hello middleware)! Check out my latest video to see the refactoring: th-cam.com/video/Yliaah4oiZY/w-d-xo.html 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: creatures.sh. 🔗 Links from the video: 👉 GitHub Repo: github.com/nikolovlazar/nextjs-clean-architecture 👉 Demo: next-clean-arch.vercel.app/ 👉 Clean Architecture article: blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html 👉 My interpretation diagram: app.eraser.io/workspace/fAyjQlkBiC7AoAoYoPnw?origin=share 👉 Configure Inversify in Next.js video: th-cam.com/video/2NVYG5VDmwQ/w-d-xo.html 👉 Test case naming guide: www.epicweb.dev/talks/how-to-write-better-test-names
I'm a self-taught developer so I only really get these types of knowledge from tutorials or just follow how these techs' documentations do it. I've watched and read different tutorials about good and clean architecture to follow but most of them are just really opinionated and not really explain why. But THIS particular video really got me. It's clean and each type of file makes sense and does different isolated work. I think I'll implement this architecture in my next projects. I mostly use Supabase and I'm excited as to how I will implement this architecture with that tech :D Thank you so much for this video!
Definitely! I think the GitHub repo is simple enough that it potentially could be used as a boilerplate, but feel free to fork it and swap out the libraries you need.
Very clear explanations! A possible improvement: Instead of binding a test setup to the concrete implementation of your application's signInUseCase, you could create a wrapper for your tests like ```function authenticatedUser(): {sessionId: string} { return { sessionId: signInUseCase({user, password}).sessionId }```. That way if the meaning of what authenticated user actually is ever changes, you don't have to change it in all the test files. It's a bit abstract with a simple example like this, but it will make more sense when you are testing behavior that depends on a certain application state. For example: If you want to test that deleted todos cannot be updated, then you will have to first create one, then delete it, and only then you can test for what you are actually interested in. Here you could have a deletedTodo() function that takes care of that setup
Thank you so much for this great video. I always heard about Clean Architecture and watched several videos, but I never truly understood it until now. After watching some videos that left me confused and even fearful of applying it, this one finally gave me a clear understanding to move forward. I love that it was demonstrated using Next.js because that's exactly what I'm learning.
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!
Not finished the video yet but as someone using clean architecture and tdd most of the time I can say that you did it pretty well. You can also enforce these principles for new developers or for bigger teams with ts-arch.
@nikolovlazar Amazing stuff... The bit on the presenter was a great thing to learn, security, ui friendlyness and performance from not shipping extra packages.
Great video, I needed it ✨, I would like to see it in a more committed project but it still helped me, I still don't understand clean architecture so I will come back again
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 ) ?
Good question! In my other project, I reuse the same Zod schema on the client-side to validate the form as well. Validating input is a *must* on the server-side, because it's easy to jump around the frontend and send data directly to the server without passing the validation mechanism. Validation on the frontend is good UX, validation on the backend is good security practice.
@@nikolovlazar Thank you for your time to response, I think I wasn't explicit enough with my question, the question was not 'to validate or not' but 'where to validate'. You validate your outgoing data inside your repository and sending back the validation error to the UI to handle it, most form libraries handle the validation in the context of the form and if the data is not valid it will never reach the repository ( repository call will never be reached in the onSubmit since the validation does not pass ). So my question is which one should be preferred as doing the latter feels good because of the proximity of the input to the form, the former makes sense in an 'architectural way' as the controller is the "gateguard" of the data 'correctness' of your application.
No I think I understood you. You should validate on both sides. As I mentioned, form validation is good UX, but you have to validate in the controller as well for security reasons. Validating in both places is a good practice. With Zod, you can even share the same validation schema. Here's how I do that in my other project: - Form: github.com/nikolovlazar/bife/blob/main/app/app/collections/create-collection.tsx#L38 - Controller: github.com/nikolovlazar/bife/blob/main/src/interface-adapters/controllers/create-collection.controller.ts#L6
@@nikolovlazar lookin at the code indeed you are double validating your outgoing data, thanks for sharing, but now then I have to ask isn't double validating redundant? ( or it is in this case but there might be a situation when something can actually happen between your action and the controller, the current example is very straightforward you pass the validated data directly into your controller, where you validate it again, i am interpreting correctly )
Double validation is not redundant. The backend and the frontend are two separate things that can live independently from each other, so each part handles validation for separate reasons. You could refactor it down the line and split it, or build a new client (ex. mobile app, AI client, whatever that uses the backend you already have) so you’ll have to refactor the server actions into api handlers. Even now, you can’t risk not validating the data on the backend, let alone in a scenario like that. Consider the backend and the frontend as two separate parts and validate data at both places. There won’t be any significant performance overheads as well.
Mind blowing. I wish I was a backend engineer I had hope to learn Architecruee stuff. I'm thankful I am Frontend Engineer, I should be disgrateful but Backend is OP.
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 ?
Correct! You can use the repository to retrieve the user object, and then you can use a service that implements the authorization check logic, so you can test that logic individually later.
Amazing video by the way. Just a question, I noticed that there are implementations where they use "domains" instead of "entities", and they put entities inside the domain. What's the difference between the two?
I haven't read a lot on it being an anti-pattern, but thanks for bringing it to my attention. Looks like you're right. If you want to avoid it, you should either write everything as classes and do property injection, or export resolved services from within the DI and not the container itself. I haven't seen any issues of using the "getInjection" method in my app yet since implementing it, but I'll have this in mind. Thanks again!
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!
Is the calculator logic plain javascript that can be used both on the frontend and backend? If it's something that you don't expect to change a lot, it's fine if there are two implementations of it (comments should note that as well). If not, see if you can isolate it in a shared function and how that would look like. You need to maintain testability, so try to follow the operation flow (contoller > use case(s) / service(s) > repository(ies)). You can have a use case that invokes the shared calculate function and use its output. That should be perfectly fine.
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!
Glad you like the content! Yep, the free Sentry account supports all SDK features, except the Insights modules (business+) and Dashboards (team+), but you'll be able to get alerts, capture exceptions, measure performance and use traces for debugging without issues on the free plan.
Using something like zsa would remove the need for input validation in the controller layer as it would be handled inside the action itself, maybe the session could also be validated inside the action and then the service would be called. This wouldn't be 100% compliant to clean arch because then the validation would belong in the framework layer and not in the interface adapters but It would be simpler to have the server action act as a controller
True! The issue with keeping that logic arises when you try to unit test that logic. Server actions should be plain functions in the test environment (haven’t checked though), but even if they are you’d have to handle ZSA-specific errors in your unit tests. If you ever need to refactor ZSA you’ll be in for a ride 😂
@@nikolovlazar Hey, I have another question, since drizzle is being used and we define all our database models there, in the entities/models the user definition is repeated and everytime we change something in the drizzle schema we need to go into the entites/models folder and change the zod schemas in order to match. I saw a library called drizzle-zod that allows zod schema generation from a drizzle table definition, maybe with this library we could define and export all the models and then in the application/entities/models we could have a class for the model and receive the it's model type in the constructor(coming from drizzle folder) and we could have access to all it's fields while also being able to define methods depending on the application's business logic. This way infrastructure concerns will be mixed up with application logic but I think this could work, what do you think about this approach?
I did use drizzle-zod briefly in this project, not sure why I didn't even mention it. Models should contain a certain amount of logic in them as well, which are basically the zod schemas. The approach you described is definitely valid, but limited to models defining their own methods, and to potentially simpler projects. In a lot of cases you would have operations that modify multiple models (tables) so that's why the isolation happens between the "business logic" and the "business rules". Logic is use cases and controllers. Rules are the models. Bigger projects would have more model classes that depend on each other, so you're very likely to run into all sorts of issues like dependency issues. Not to mention, once you start bringing in other model classes in one model class, you're starting to create a "god class" (a.k.a. "god object"), which is a code smell and something you should avoid.
Great video and explanation of each layer. I have one next.js specific question: should we prioritize server actions over route handlers? Are there any benefits to be gained from choosing one over the other?
Thanks! Server Actions are faster to develop because they’re functions. Route Handlers are exposing an endpoint that can be triggered by any client, not just Next.js. I think when using Actions the app performs a rerender automatically to update the UI with the new data, so there’s that. If you’re building a Next.js app that will remain just a Next.js app (and not have other clients use your API) you can stick with actions. Even if you need to change in the future, you’ll just cut the try catch block that invoked the controller and refactor handling the return and exceptions.
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?
Glad you liked it! And that's a good question! I think the only thing forcing the libraries to push updates (once they achieved what they want to achieve) is the language itself. As TypeScript / JavaScript evolves and introduces breaking changes, they most likely would need to push an update. That doesn't happen as often, so that's why you don't see any activity on these repositories for an extended period of time. On the other hand, just like any other library dependency, there is always a risk of deprecation and refactoring.
Awesome video, could you please extend cover below things in second part? In Clean Architecture Where/how will handle global state like zustand or redux toolkit? Where/how will handle constants and enums? Where/how will handle language translation (for errors)? Where/how will use Where/how to map the types (separate types for Infra other layer)
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?
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.
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
Thank you! I'm working on a different DI approach that would work in other runtimes and I'll most likely publish a separate video on it, so stick around for that one! Right now you'd have to patch things in the middleware function.
Pleasant to watch bringing some order to usual Nextjs chaos. Really good ideas and realisation here. One thing i don't get fully is multiple controllers. Why is there createTodoController/getTodosForUserController/toggleTodoController (which acts like 3 different controllers) and not single TodoController with create/getTodosForUser/toggle methods?
Happy that you found it useful! In regards to the controller, you're talking about a class versus functions. Since we're in JavaScript, functions feel easier to work with so I went with functions. Classes need to be instantiated before you use them so it represents an extra step. If we utilized constructor injection for the DI, then we would've had to use a class.
@@nikolovlazar Thanks for the answer. Probably i'm just brain damaged by other MVC frameworks, hard to see a controller that is not an object 😅. The easiest way to avoid instantiation would be to have maybe index.js in the controller directory that reexports all functions in single object or single file that has only default export of an object containing all the methods. In theory static classes can also help to avoid instantiation but still allow extension. Some repeated logic (error handling for example) could live on the base Controller class. This will also allow to have decorators for methods, for example "Authenticatable" that could check sessionId and get the user. Thanks again for the content.
did you ever try to implement this in a bigger project? i recently tried it and it was so much over head creating all the dtos. and especially if you have nested entities it is just a pain doing this. and for one small change i need to touch a lot of files. this seems more like a academic philosphising about architecture. maybe if you have a big team. but not as a solo dev.
I have a couple of shorts on my channel where I address this, but in short - yes. If you’re a solo dev and don’t care about testing or growing the team or the codebase, then you won’t see too much value in CA.
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!
@@nikolovlazar Thanks for the early response. But what if the Prisma transaction needs to occur across two different entities? For example: updating the user table to deduct credits and simultaneously decreasing the stock of a product purchased with credits. I understand that products will have their own repository, and users will have their own repository. However, implementing this logic in a useCase without introducing Prisma in application layer will not allow for a Prisma transaction, and each query will run separately, causing us to lose atomicity in the operation.
that would be two different use cases. Use cases are single operations. Updating a field in the users table is one use case. Deducing credits in a different table is another. You’ll have a controller that “orchestrates” those two use cases based on the input, user, etc… the controller will invoke them.
@@nikolovlazar I am not the person you are replying to, but I assume you mean that the controller will combine those two use cases in a transaction and thus make the transaction atomic?
@@FTheohuoooh I see now. If that’s the case then I guess it has to be part of a repository. If those two or more db operations happen every single time, it does make sense to put them in one transaction in a repository and invoke that. Another approach I could think of is splitting the query building from executing. This is a wild idea and I’m just thinking out loud, but what if the repositories just build your query(ies), and then you pass it off to a service that executes all of them in a single database transaction? I’m not sure of what side effects can that have on testing, but it’s something to think about 😁
Thanks! Everything would fall into the Frameworks & Drivers layer. TRPC's queries (equivalent to Next.js Server Actions in a way) would be defined in the "app" directory alongside everything Next.js related, and they will invoke controllers. Exactly the same as the server actions I presented in this video.
Yep! Since you don't want to return entitie types, you create a UI-friendly object that contains the controller's results, which could be multiple entities as well. That's basically a DTO.
Hey friend, I have an application with a large userbase, over 300k. I find that Sentry captures so many random JS errors that are unhandled and I have no idea why/what they do. It's like missing 'a' in some random js bundle, or scrollEvent, or some extension stuff. It consumes so many of my error metrics and it does nothing for me, what do you suggest I do in this case? I only want to capture relevant errors.
Hey! You should check out the Filtering doc: docs.sentry.io/platforms/javascript/guides/nextjs/configuration/filtering/ You can try the "thirdPartyErrorFilterIntegration" method. That way you "fingerprint" your codebase's bundles and only errors that originate from those bundles get reported, otherwise they get ignored. This + the "ignoreErrors" regex can significantly reduce issues noise. Let me know if this works for you!
Your codebase won't need to change too much (aside from your repository, which is the reason why you want to put away that implementation detail). If you want to use MySQL instead of SQLite, you'd need to start a mysql server alongside your app, and the easiest way to do that is to use Docker Compose. There's plenty of resources on how to get started with Docker and Docker Compose.
@@nikolovlazar I watched this video without skipping anything. It helped me understand the structure, but the folder structure is too complicated and there are many libraries that I am using for the first time, so it is difficult to create a project from scratch and follow along. Still, I will try to follow along little by little.
@@김인욱-u5q A good rule of thumb is this: - if the library you use lives around the "consumer" part of the app (next.js, api handlers etc...) it belongs in the Frameworks layer. - if the library you use supplements the app's core logic (a database, auth, email service, APIs that your app uses...), it lives in the Infrastructure layer. It'll be either in Frameworks or Infra. There's no other place where you put third-party libraries.
@@김인욱-u5q Oh you meant there are many libraries in this video that you're using for the first time. Sorry I misunderstood your comment. Don't hesitate to ask me specific questions about the setup and libraries. If you want more real-time help, feel free to join my Discord server and we can discuss in there: creatures.sh.
Update: I refactored the app and now we're running on any runtime, including Vercel's Edge (hello middleware)! Check out my latest video to see the refactoring: th-cam.com/video/Yliaah4oiZY/w-d-xo.html
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: creatures.sh.
🔗 Links from the video:
👉 GitHub Repo: github.com/nikolovlazar/nextjs-clean-architecture
👉 Demo: next-clean-arch.vercel.app/
👉 Clean Architecture article: blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
👉 My interpretation diagram: app.eraser.io/workspace/fAyjQlkBiC7AoAoYoPnw?origin=share
👉 Configure Inversify in Next.js video: th-cam.com/video/2NVYG5VDmwQ/w-d-xo.html
👉 Test case naming guide: www.epicweb.dev/talks/how-to-write-better-test-names
I'm a self-taught developer so I only really get these types of knowledge from tutorials or just follow how these techs' documentations do it. I've watched and read different tutorials about good and clean architecture to follow but most of them are just really opinionated and not really explain why. But THIS particular video really got me. It's clean and each type of file makes sense and does different isolated work. I think I'll implement this architecture in my next projects. I mostly use Supabase and I'm excited as to how I will implement this architecture with that tech :D Thank you so much for this video!
I'm glad you fount this useful! Here's an other project of mine that implements CA with Supabase: github.com/nikolovlazar/bife
@@nikolovlazar understanding the comment and giving him what he needs. Great efforts sir. 🫡🙌
Bro, you saved my life! I'm struggled importing the reflect-metadata on NextJS all the time to implement dependency injection!
Having this clean architecture as boilerplate to start a new complex project, I think would be cool, with different packages like supabase etc
Definitely! I think the GitHub repo is simple enough that it potentially could be used as a boilerplate, but feel free to fork it and swap out the libraries you need.
Very clear explanations!
A possible improvement:
Instead of binding a test setup to the concrete implementation of your application's signInUseCase, you could create a wrapper for your tests like ```function authenticatedUser(): {sessionId: string} { return { sessionId: signInUseCase({user, password}).sessionId }```. That way if the meaning of what authenticated user actually is ever changes, you don't have to change it in all the test files.
It's a bit abstract with a simple example like this, but it will make more sense when you are testing behavior that depends on a certain application state. For example: If you want to test that deleted todos cannot be updated, then you will have to first create one, then delete it, and only then you can test for what you are actually interested in. Here you could have a deletedTodo() function that takes care of that setup
Good point! Thanks!
Thank you so much for this great video. I always heard about Clean Architecture and watched several videos, but I never truly understood it until now. After watching some videos that left me confused and even fearful of applying it, this one finally gave me a clear understanding to move forward. I love that it was demonstrated using Next.js because that's exactly what I'm learning.
Thank you! I'm glad that you found it useful!
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!
Not finished the video yet but as someone using clean architecture and tdd most of the time I can say that you did it pretty well. You can also enforce these principles for new developers or for bigger teams with ts-arch.
Thank you! Someone on my live streams told me about ts-arch. I checked it out, but haven't used it yet.
Thanks for creating it. It blows my mind. Need such content more on your channel.
One of the best and insightful videos that I have seen the last years in YT. Thank you man!
@nikolovlazar Amazing stuff... The bit on the presenter was a great thing to learn, security, ui friendlyness and performance from not shipping extra packages.
My brain is currently stop working🤯
Would love to see this applied for a React + Vite SPA application!
That would be an interesting topic. CA on the client side!
this is what quality content look like. thanks!
Thank you! And thank you for becoming a member! 💖
Haven't watched the entire video yet but the diagram alone was SUPER helpful and needed, Thanks a lot. Wish I could give you multiple likes
Thanks a lot! Happy that you found it useful!
Изключително добре направено! Браво!
Great video, I needed it ✨, I would like to see it in a more committed project but it still helped me, I still don't understand clean architecture so I will come back again
WOW!! So much bonus in that video!! Thank you so much!!! You made life easy for JS dev! 🔥🔥🔥
This is pure gold. Thank you so much!
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 ) ?
Good question! In my other project, I reuse the same Zod schema on the client-side to validate the form as well. Validating input is a *must* on the server-side, because it's easy to jump around the frontend and send data directly to the server without passing the validation mechanism. Validation on the frontend is good UX, validation on the backend is good security practice.
@@nikolovlazar Thank you for your time to response, I think I wasn't explicit enough with my question, the question was not 'to validate or not' but 'where to validate'. You validate your outgoing data inside your repository and sending back the validation error to the UI to handle it, most form libraries handle the validation in the context of the form and if the data is not valid it will never reach the repository ( repository call will never be reached in the onSubmit since the validation does not pass ). So my question is which one should be preferred as doing the latter feels good because of the proximity of the input to the form, the former makes sense in an 'architectural way' as the controller is the "gateguard" of the data 'correctness' of your application.
No I think I understood you. You should validate on both sides. As I mentioned, form validation is good UX, but you have to validate in the controller as well for security reasons. Validating in both places is a good practice. With Zod, you can even share the same validation schema. Here's how I do that in my other project:
- Form: github.com/nikolovlazar/bife/blob/main/app/app/collections/create-collection.tsx#L38
- Controller: github.com/nikolovlazar/bife/blob/main/src/interface-adapters/controllers/create-collection.controller.ts#L6
@@nikolovlazar lookin at the code indeed you are double validating your outgoing data, thanks for sharing, but now then I have to ask isn't double validating redundant? ( or it is in this case but there might be a situation when something can actually happen between your action and the controller, the current example is very straightforward you pass the validated data directly into your controller, where you validate it again, i am interpreting correctly )
Double validation is not redundant. The backend and the frontend are two separate things that can live independently from each other, so each part handles validation for separate reasons. You could refactor it down the line and split it, or build a new client (ex. mobile app, AI client, whatever that uses the backend you already have) so you’ll have to refactor the server actions into api handlers. Even now, you can’t risk not validating the data on the backend, let alone in a scenario like that. Consider the backend and the frontend as two separate parts and validate data at both places. There won’t be any significant performance overheads as well.
Thanks for the great video lazar.
Watched more than 5 streams then didn't have time to watch more glade that you dropped this one.
This video will save you HOURS 😂 thanks!
Awesome bro, we absolutely need more of this excellent content. subscribed for more of this! thank you!
Thank you!
Best Clean Architecture video ever, thank you 🙏🏻
Glad you liked it!
Mind blowing. I wish I was a backend engineer I had hope to learn Architecruee stuff. I'm thankful I am Frontend Engineer, I should be disgrateful but Backend is OP.
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 ?
Correct! You can use the repository to retrieve the user object, and then you can use a service that implements the authorization check logic, so you can test that logic individually later.
This is a very detailed video about clean arch, very well made. thanks
Amazing video by the way. Just a question, I noticed that there are implementations where they use "domains" instead of "entities", and they put entities inside the domain. What's the difference between the two?
Great video. Quick question. Isn't the `getInjection` helper function an example of the service locator anti-pattern that InversifyJS warns against?
I haven't read a lot on it being an anti-pattern, but thanks for bringing it to my attention. Looks like you're right. If you want to avoid it, you should either write everything as classes and do property injection, or export resolved services from within the DI and not the container itself. I haven't seen any issues of using the "getInjection" method in my app yet since implementing it, but I'll have this in mind. Thanks again!
basically a standard Rest API (repository, service, controller) plus UI :)
Pretty much 😁 with some additional rules on top
Great video. Extra respect for Neovim.
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!
Is the calculator logic plain javascript that can be used both on the frontend and backend? If it's something that you don't expect to change a lot, it's fine if there are two implementations of it (comments should note that as well). If not, see if you can isolate it in a shared function and how that would look like. You need to maintain testability, so try to follow the operation flow (contoller > use case(s) / service(s) > repository(ies)). You can have a use case that invokes the shared calculate function and use its output. That should be perfectly fine.
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!
Glad you like the content! Yep, the free Sentry account supports all SDK features, except the Insights modules (business+) and Dashboards (team+), but you'll be able to get alerts, capture exceptions, measure performance and use traces for debugging without issues on the free plan.
Using something like zsa would remove the need for input validation in the controller layer as it would be handled inside the action itself, maybe the session could also be validated inside the action and then the service would be called. This wouldn't be 100% compliant to clean arch because then the validation would belong in the framework layer and not in the interface adapters but It would be simpler to have the server action act as a controller
True! The issue with keeping that logic arises when you try to unit test that logic. Server actions should be plain functions in the test environment (haven’t checked though), but even if they are you’d have to handle ZSA-specific errors in your unit tests. If you ever need to refactor ZSA you’ll be in for a ride 😂
@@nikolovlazar Hey, I have another question, since drizzle is being used and we define all our database models there, in the entities/models the user definition is repeated and everytime we change something in the drizzle schema we need to go into the entites/models folder and change the zod schemas in order to match. I saw a library called drizzle-zod that allows zod schema generation from a drizzle table definition, maybe with this library we could define and export all the models and then in the application/entities/models we could have a class for the model and receive the it's model type in the constructor(coming from drizzle folder) and we could have access to all it's fields while also being able to define methods depending on the application's business logic. This way infrastructure concerns will be mixed up with application logic but I think this could work, what do you think about this approach?
I did use drizzle-zod briefly in this project, not sure why I didn't even mention it. Models should contain a certain amount of logic in them as well, which are basically the zod schemas. The approach you described is definitely valid, but limited to models defining their own methods, and to potentially simpler projects. In a lot of cases you would have operations that modify multiple models (tables) so that's why the isolation happens between the "business logic" and the "business rules". Logic is use cases and controllers. Rules are the models. Bigger projects would have more model classes that depend on each other, so you're very likely to run into all sorts of issues like dependency issues. Not to mention, once you start bringing in other model classes in one model class, you're starting to create a "god class" (a.k.a. "god object"), which is a code smell and something you should avoid.
Great video and explanation of each layer. I have one next.js specific question: should we prioritize server actions over route handlers? Are there any benefits to be gained from choosing one over the other?
Thanks! Server Actions are faster to develop because they’re functions. Route Handlers are exposing an endpoint that can be triggered by any client, not just Next.js. I think when using Actions the app performs a rerender automatically to update the UI with the new data, so there’s that. If you’re building a Next.js app that will remain just a Next.js app (and not have other clients use your API) you can stick with actions. Even if you need to change in the future, you’ll just cut the try catch block that invoked the controller and refactor handling the return and exceptions.
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?
Glad you liked it! And that's a good question! I think the only thing forcing the libraries to push updates (once they achieved what they want to achieve) is the language itself. As TypeScript / JavaScript evolves and introduces breaking changes, they most likely would need to push an update. That doesn't happen as often, so that's why you don't see any activity on these repositories for an extended period of time. On the other hand, just like any other library dependency, there is always a risk of deprecation and refactoring.
very good viedo. thanks! :)
Awesome video, could you please extend cover below things in second part?
In Clean Architecture
Where/how will handle global state like zustand or redux toolkit?
Where/how will handle constants and enums?
Where/how will handle language translation (for errors)?
Where/how will use
Where/how to map the types (separate types for Infra other layer)
Thank you! These are good points. I'll definitely cover some of these in future.
По случайност ти намерих канала, точно преди седмичният stand up 😂 Keep up the quality content 👌
Thank you! Благодаря ви!
Very educative video. Create a larger project implementing the same
Thanks! It’s not a seriously large project, but I have it implemented here as well: github.com/nikolovlazar/bife
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?
Thank you! Not sure if I’ve seen anything like that. Hop into creatures.sh and we can figure it out!
Like, awesome! Please, more videos on architecture topic
And sentry tutorials too!
Thanks!
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.
Thanks! I had to take a shortcut there. It's not CA-approved 😁 We should put that away in its own service and call the service instead.
Very good stuff! Thank you
Thank you for the cool video! It was very interesting!
Glad you liked it!
What nvim theme are you using?
Also, great video 🫡
Thanks! It’s cyberdream with a custom background. Check it out here: My Neovim Dev Workflow
th-cam.com/video/G7-qUMKSH_Y/w-d-xo.html
Thanks for the great work!
Glad you like it!
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
Thank you! This is not vscode, but neovim. Here’s my config video: th-cam.com/video/G7-qUMKSH_Y/w-d-xo.htmlsi=kq2lTPyJaQqa9JTl
This is gold.
Maan thank you for your work. Greetings from Ukraine
Great video to explain clean arch! Im wondering that how to deal with stuff like internationalization etc that need middleware?
Thank you! I'm working on a different DI approach that would work in other runtimes and I'll most likely publish a separate video on it, so stick around for that one! Right now you'd have to patch things in the middleware function.
Pleasant to watch bringing some order to usual Nextjs chaos. Really good ideas and realisation here.
One thing i don't get fully is multiple controllers. Why is there createTodoController/getTodosForUserController/toggleTodoController (which acts like 3 different controllers) and not single TodoController with create/getTodosForUser/toggle methods?
Happy that you found it useful! In regards to the controller, you're talking about a class versus functions. Since we're in JavaScript, functions feel easier to work with so I went with functions. Classes need to be instantiated before you use them so it represents an extra step. If we utilized constructor injection for the DI, then we would've had to use a class.
@@nikolovlazar Thanks for the answer. Probably i'm just brain damaged by other MVC frameworks, hard to see a controller that is not an object 😅. The easiest way to avoid instantiation would be to have maybe index.js in the controller directory that reexports all functions in single object or single file that has only default export of an object containing all the methods.
In theory static classes can also help to avoid instantiation but still allow extension. Some repeated logic (error handling for example) could live on the base Controller class. This will also allow to have decorators for methods, for example "Authenticatable" that could check sessionId and get the user.
Thanks again for the content.
Oh that's a good point!
did you ever try to implement this in a bigger project? i recently tried it and it was so much over head creating all the dtos. and especially if you have nested entities it is just a pain doing this. and for one small change i need to touch a lot of files. this seems more like a academic philosphising about architecture. maybe if you have a big team. but not as a solo dev.
I have a couple of shorts on my channel where I address this, but in short - yes. If you’re a solo dev and don’t care about testing or growing the team or the codebase, then you won’t see too much value in CA.
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!
The "ErrorOptions" interface is built-in the language actually, just like the "Error" one.
Awesome work, thank you for the video lazar. I have a question. How would you handle Prisma transactions in this code structure?
Thank you! Everything Prisma related would be boxed in a Repository.
@@nikolovlazar Thanks for the early response. But what if the Prisma transaction needs to occur across two different entities? For example: updating the user table to deduct credits and simultaneously decreasing the stock of a product purchased with credits. I understand that products will have their own repository, and users will have their own repository. However, implementing this logic in a useCase without introducing Prisma in application layer will not allow for a Prisma transaction, and each query will run separately, causing us to lose atomicity in the operation.
that would be two different use cases. Use cases are single operations. Updating a field in the users table is one use case. Deducing credits in a different table is another. You’ll have a controller that “orchestrates” those two use cases based on the input, user, etc… the controller will invoke them.
@@nikolovlazar I am not the person you are replying to, but I assume you mean that the controller will combine those two use cases in a transaction and thus make the transaction atomic?
@@FTheohuoooh I see now. If that’s the case then I guess it has to be part of a repository. If those two or more db operations happen every single time, it does make sense to put them in one transaction in a repository and invoke that.
Another approach I could think of is splitting the query building from executing. This is a wild idea and I’m just thinking out loud, but what if the repositories just build your query(ies), and then you pass it off to a service that executes all of them in a single database transaction? I’m not sure of what side effects can that have on testing, but it’s something to think about 😁
Great video!! I wonder how this can apply to TRPC specifically the T3 stack.
Thanks! Everything would fall into the Frameworks & Drivers layer. TRPC's queries (equivalent to Next.js Server Actions in a way) would be defined in the "app" directory alongside everything Next.js related, and they will invoke controllers. Exactly the same as the server actions I presented in this video.
@@nikolovlazar Thanks, this is really helpful!!
@@seifmahdy4846 You're welcome! If you have more questions don't hesitate to ask. I'm always around in my Discord server creatures.sh.
zod for entities should be the norm hahah
Right? 😂
@@nikolovlazar ofc!!
At last, Thank you
Love the vid, thnx!
Are the objects returned by your presenters essentially DTO's?
Yep! Since you don't want to return entitie types, you create a UI-friendly object that contains the controller's results, which could be multiple entities as well. That's basically a DTO.
Hey friend, I have an application with a large userbase, over 300k. I find that Sentry captures so many random JS errors that are unhandled and I have no idea why/what they do. It's like missing 'a' in some random js bundle, or scrollEvent, or some extension stuff. It consumes so many of my error metrics and it does nothing for me, what do you suggest I do in this case? I only want to capture relevant errors.
Hey! You should check out the Filtering doc: docs.sentry.io/platforms/javascript/guides/nextjs/configuration/filtering/
You can try the "thirdPartyErrorFilterIntegration" method. That way you "fingerprint" your codebase's bundles and only errors that originate from those bundles get reported, otherwise they get ignored. This + the "ignoreErrors" regex can significantly reduce issues noise. Let me know if this works for you!
Wow , thanks ❤
Which browser are you using?
It's Brave
what theme is this in nvim?
This is Cyberdream! github.com/nikolovlazar/dotfiles. I also have a video on my setup: th-cam.com/video/G7-qUMKSH_Y/w-d-xo.html
Top!
Can we hire you?
Awesom
why we need sqllite instead mysql
I picked SQLite just to make the demo project simpler to run locally, no other reason.
@@nikolovlazar So if we use MySQL and hit an API, would it be challenging technically? Or do you have any suggestions?
Your codebase won't need to change too much (aside from your repository, which is the reason why you want to put away that implementation detail). If you want to use MySQL instead of SQLite, you'd need to start a mysql server alongside your app, and the easiest way to do that is to use Docker Compose. There's plenty of resources on how to get started with Docker and Docker Compose.
It seems way too angular-like which I don't really accept when I work on nextjs this kind of framework based on react...
That's really cool, but it's also complicated :(
Step by step! It's important to understand the reasons why each layer exists, and why you want to split your app's codebase in layers.
@@nikolovlazar I watched this video without skipping anything. It helped me understand the structure, but the folder structure is too complicated and there are many libraries that I am using for the first time, so it is difficult to create a project from scratch and follow along. Still, I will try to follow along little by little.
@@김인욱-u5q A good rule of thumb is this:
- if the library you use lives around the "consumer" part of the app (next.js, api handlers etc...) it belongs in the Frameworks layer.
- if the library you use supplements the app's core logic (a database, auth, email service, APIs that your app uses...), it lives in the Infrastructure layer.
It'll be either in Frameworks or Infra. There's no other place where you put third-party libraries.
@@nikolovlazar I'll keep that in mind. Thank you
@@김인욱-u5q Oh you meant there are many libraries in this video that you're using for the first time. Sorry I misunderstood your comment. Don't hesitate to ask me specific questions about the setup and libraries. If you want more real-time help, feel free to join my Discord server and we can discuss in there: creatures.sh.
this is nesxt js
First!
🙌
Genius!!!!!