Node Monorepos with Nx


Multiple apis and multiple teams all in the same repository can cause a lot of headaches, but Nx has you covered. Learn to share code, maintain configuration files and coordinate changes in a monorepo that can scale as large as your organisation does. Nx allows you to bring structure to a repository with hundreds of contributors and eliminates the CI slowdowns that typically occur as the codebase grows.

Table of contents:

- Lab 1 - Generate an empty workspace

- Lab 2 - Generate a node api

- Lab 3 - Executors

- Lab 4 - Migrations

- Lab 5 - Generate an auth library

- Lab 6 - Generate a database library

- Lab 7 - Add a node cli

- Lab 8 - Module boundaries

- Lab 9 - Plugins and Generators - Intro

- Lab 10 - Plugins and Generators - Modifying files

- Lab 11 - Setting up CI

- Lab 12 - Distributed caching


All right. Okay. So, welcome to Node monorepos with NX. This is great. So, I am, my name is Isaac Mann. I'm an architect at NX. We've been, I've been working with NX for four years. I used it for two years before that. And yeah, this is exciting. So, this workshop will be tailored towards Node as just the framework that we'll be using with NX. But NX is, you know, is pretty much framework and language agnostic. But we'll get into that more. So, you can use front-end applications like react or angular or, you know, whatever other front-end technology you're using. You can also use other languages like, obviously, people use Java and Python and.NET with NX. So, NX is all about, you know, managing your, managing your code base. And whatever code you're using is up to you. This is Node monorepos with NX. And first, we're gonna start with a little discussion of why you would want a monorepo. So, a monorepo is any repository that has more than one application living in that codebase. So, what's the benefit of a monorepo? So, three kind of big categories of benefits for monorepos. monorepos give you atomic changes, allow you to easily share your code and give you a single set of dependencies, make it easier to manage those dependencies. So, we're gonna dig into each of these three things and explain what they are and why they're beneficial. So, if you had an application and a kind of a library that is used by that application, and if you have them managed in two separate repos. Let's say you make a change to the UI library that's in repo two at the bottom here. If you make a change to it and for some reason it breaks the home page application, you, the life cycle of that change would be, so, somebody makes a change to the common UI library, they push up the commit, they incommit the version. At some point later, somebody tries to use that new version of the common UI library in the home page app. They realize there's a breaking change there and it broke their application. So, they file a bug with the common UI library and they say, hey, you got to fix this. And then later, once the UI library developer sees that problem, they make the change, they push a new fix up, new version, and then the home page app people later have to bump the version to the fixed version and then see if it fix their application. So, that whole cycle, typically, I mean, the best case scenario will take a couple days. It can take a week or two if people are not working on these things full time. So, one of the issues here is, if you were to put both those library, the library and the application together in the same repo, then that cycle would be, it would be just the UI library creator making a change, running the tests and seeing, oh, it broke the test in the home page app, I've got to fix that. And that, so that cycle took, will take like a half hour or an hour. Like, even before you've pushed up a PR, you'd notice that you broke the application, you have to make, you know, modify your code. So, that's, so you have in one PR, you know, whether, you know, all the changes that you need to make to the application or changes that, you know, you need to make the UI library to adapt to the application itself. So, that's one benefit of a monorepo. Second benefit is shared code. Let's say you have some logic about, you know, what is a valid username. And, you know, this function, it's, you know, obviously the different scenarios, you'd have a, you know, more complex function than this, you know, handling whether, whether username was valid. But let's say you want to share this across your application, across, you know, multiple application, multiple libraries. If this ever were to change, you'd have to update that in every single repo that's using it. If you're, if you're copying the code from repo to repo. Whereas if you're in a monorepo setting, you can just use this function. And whenever that function changes, then your, your behavior changes across the whole, the whole repository, wherever it's being used. So, easily sharing code. Third thing is having a single set of dependencies. So, if you have a framework like Node, or in this case, and the picture is react, if you have different versions of that framework being used in different applications, you can run into very strange, hard to debug runtime errors. And, and having your code in the same repository, will basically forces you to have, well, it can, you can set rules in place to force yourself to be on the same version for everything that's in that repo. It is possible to have multiple versions of the same monorepo, but it's, it's better to have a single version and makes things easier in the long run. So, basically, in a large, when you have multiple applications, typically you have one application that you're working on all the time, that is on the latest version of whatever dependencies you have. But if you, and you're going to have, you know, two or three other applications that get worked on maybe once every couple months, and those inevitably fall behind and you, you have to remember, okay, how did I upgrade that framework? You know, the six months ago upgrade, and you have to remember and go through all that same work again. Whereas if it was, if you were upgrading all your applications, all three applications at the same time, it's a lot easier than upgrading three different applications at different points over the course of a year and a half. So, you just do it all at once, and it's a lot easier than, than doing it spread out over a year or whatever, whenever you think of updating those applications. All right. So, one way of, you know, having a, a monorepo. So, the difference between a monorepo and code collocation. Code collocation is when you just dump all the applications together in the same repo without having any tooling to manage that. And that, that can be a nightmare. If you just dump it all together, you can end up with a scenario where you're running unnecessary tests, where you have no code boundaries, and all the code just gets mixed up together, and it's hard to maintain. And you can have inconsistent tooling, where you have conflicts between how those tools interact with each other, like your testing tools or your, whatever other tools you have. So, let's, let's talk about running unnecessary tests. So, let's say you have a library called the products homepage that's, that's using the shared product UI. If you were to make a change just to the products homepage project, you know that you need to run the tests for that, for that project to make sure that you didn't break anything. But you know, you also know that you don't have to run the tests on the shared product UI, because you know there's no possible way you could have broken the test on the shared product UI if the only thing you changed was this top project. So, if you have no tooling set up in your, in your repository, that kind of understands your, your dependency graph here, in order to make sure you didn't break anything, you would have to run the tests for everything in your repo, anytime you make a change anywhere in your repo. So, it'd be better to have some tooling to understand that I only need to run tests on the products homepage, not on the shared product UI. And this obviously is a, you know, that's a very simple scenario, a real repo, and this is even a small repo here, this example. You're, you know, a more typical setup would be something like this, where you're, you have lots of different nodes, and you need some, you know, some computer to understand what, which tests do I actually need to run, rather than having a person try to remember all these different dependency graph lines, to know that, you know, I have to run these tests and not these tests. So, you want to make sure to run all the tests you need to run, but not the tests that you don't have to run. All right, number two, no code boundaries. So, if you just dump everything together, you might have some code that you write for in your, like, UI library that is just intended for you to use yourself, for you to use within that project, and you're not ready for other people to start using it yet. You don't want to maintain that api surface. But there's nothing stopping people from reaching into your project and using some function that you only want for yourself, but they say, oh, there's code that I like, I want to just use it. And now, because they're using it, you're stuck with maintaining that api, and you can't change it, because if you change it, you'll break their application. So, you need some tooling set up to say, these are the functions that I'm okay with other people using, and these are functions that are purely internal and only for my project to use. The other thing is, you can have inconsistent tooling. So, typically, with the npm scripts, they just get named haphazardly, and there's a bunch of flags thrown in there, and nobody knows exactly what all the flags do. Nobody knows exactly what all the scripts really do. So, you've got different versions of build and test and serve and lint, and they're set up for different scenarios and different eventualities, but it's really hard, unless you're working on that project every day, it's really hard to know what all those scripts actually do. So, it'd be nice to have some consistent rules and some just conventions about what those things should be named and how to easily know what each flag needs in your scripts. All right, so you need some tooling to help you manage that complexity. So, that's what NX can do. NX is the tooling to help you get all the benefits of a monorepo without the negatives of code co-location. All right, so NX gives you faster command execution. So, executors are basically like npm scripts that can help you run your build and test and lint and all the actions that you take on your code. NX affected is the tool that lets you run the tests for only the projects that are affected by a code change and not the tests that are unrelated. And then you have local and distributed caching. So, anytime you run a command and if you run that same command again without changing the code, it just uses the, instead of actually running the build again, it just uses the cache version of that output and without actually running the build again. So, if your build takes a minute and a half, if you run that build again, instead of taking a minute and a half, it'll take a second or two just to pull that data from the cache and to replay the terminal output for you. So, that's just locally, that's completely free, that's in the open source NX. Then with NX cloud, you can use the distributed caching. So, that same caching behavior that you get on your own machine, you can share that cache across your whole organization. So, if anybody anywhere has run this command with this same code, you can use their cache and replay it yourself. So, this, you know, particularly for PRs, you run your code, you push it up, it runs in CI, and then somebody else checks that branch out on their machine and they run the same thing, they can just use your cached version instead of running that same, you know, computation on their machine again. So, basically, you only run the actions for a particular set of code one time across your whole organization, which is, and that's also, it's not just for, that's broken up by project. So, if you have one application that depends on five different libraries, that's the library build step is cached, as well as the application build step. So, say you change just the application, those five libraries, their build output is already cached for you. And then, so you can just use that cache and then only do the last application build step, which can be game-changing for large codebases. NX also gives you controlled code sharing. So, you set an index.ts file at the root of your, each project, and you say everything exported from this index.ts file is available for other people to use. But anything that's, you know, not exported from that index.ts, you're not allowed to reach in there and grab it and start using it. You can set up a tagging system to say these particular projects can depend on other projects. It's very flexible. You can set up your own structure of how those tags work. So, these libraries belong to this team, and these libraries belong to this other team, this other team, and then these libraries are shared, or shared, and they can be used by either. So, you can set up that whole structure yourself. You can publish, easily publish libraries to npm, and you can use the code owners file to, you know, require, this is a Git feature, GitHub feature, and any of it, like GitLab and other, all the other, like, code management Git repositories has the same functionality with code owners, where you force a review from a certain group of people if you change code within that area. So, you can control how that code is managed. Number three is consistent coding practices. So, you can, you know, there's, NX gives you some Lint rules to help, you know, manage structure. You can write code generators to automate the steps that people, the developers take over and over again. There are plugins that are, that NX creates, and plugins created by the community that have these generators and executors written for you. So, the plugins help you with, basically, that you can stop worrying about your build system if you want to. You can have the plugin manage your build system, and then just make whatever tweaks you need to for your particular application. Plugins also help you with automating the update process. So, when a, when there's a new version of Node that comes out, if there's a breaking change, or if there's a new version of Express or whatever, there's a migration script that can automatically update your code to handle any breaking changes that happen. And same thing with community plugins, they're community plugins that cover use cases that NX does not. Then the other thing is NX gives you an accurate architecture diagram of how your projects depend on each other. So, this is useful for the NX effective command, but it's also useful for yourself to really see, okay, what is the actual architecture of my code, rather than the architecture that I wish my code had, or the architecture that my code had two months ago when somebody drew a diagram. So, NX actually understands your code and can draw the connecting dependency lines between your projects based on what's actually there, rather than what you, or some, what some architect actually, like, wished was there, or thought should be there. Cool. Yeah, so this is a typical NX file structure. Really, your file structure can be whatever you want, but basically, the way we'll set it up is we'll have an apps folder and a libs folder. And then, yeah, dist is where your, you know, applications get built to. Tools is for, like, scripts and helper things. Workspace.json is not valid anymore. Nx.json is like your root level configuration for NX itself. tsconfig, that sets up kind of like paths for your libraries to automatically, so you can reference them to import them into your applications. For all the different kind of configuration that NX has, there's workspace level and project level. So, the project level configuration will inherit from the workspace level configuration. So, you've got your base typescript configuration for the whole workspace, and then each project can overwrite that for its own, if it has a different typescript setting that it needs. Same thing for Jest and Prettier and all the other tools that NX can set up for you. All right. So, that was a lot of talking. We're going to get started with creating a new workspace. So, to create a new workspace, you run npx create NX workspace, or you can do the same thing with yarn or PNPM, and you give it a name. So, in this case, the name is, sets three different things. It'll set what directory that you're putting your code in. It'll give you the path alias for importing code within things. So, if you set your workspace name to my org, your directory will be under my-org. Your path alias will be at my org slash whatever project you're importing. And then, if you ever publish your library to npm, the npm scope will be at my org slash whatever library name you have. So, each of these three things you can change after the fact, but when you initially set up your workspace, whatever you type in after create NX workspace will be set to those. Those two things will be set to that name. Okay. So, let's get started with lab one. Okay. So, this repo here, you do not need to- you don't- do not need to clone this repo, because we're going to be creating a whole new repo for the- for the workshop. So, we're going to go to lab one here. Generate an empty work- NX workspace for a fictional company called the Board Game Horde, and we're going to name it bg-horde. All right. So, I'm going to run npx create NX workspace, and we're going to name it bg-horde. Okay. All right. Let me- let me make this full screen so we can read it. Okay. You know what? I have an old version of create NX workspace, so I'm going to do at latest here. There we go. So, this is what you should see. So, there- it gives you some- some options here to pick from. Package-based is the setup where you have- basically, you're managing your packages with package json files, and it's more freeform, and basically lets you set up your own build system the way- whatever way you want, and NX kind of gets out of your way unless you do whatever you want. For our- for our workshop here, we're going to use the integrated setup, which you- it kind of has a setup structure with apps and libs, and is more- is- the- the idea with an integrated monorepo is that you- you- you buy into the whole NX ecosystem, and you use- you're using plugins to manage your- your build system and your lint system, and then- and it's set up so you can have, you know, multiple applications in the same- same repo. The- these last three options, standalone, react-angular, and node-app, these are ones where you're- you have- where you- you have basically one application in your repo, and then multiple- multiple libraries that are used by that application. And so, with- with the standalone setup, you have one application at the root of your repo, and then- and then libraries kind of next to it. But for- for our workshop, we're going to use integrated monorepo, and then there's several other app options here. So, apps is a- basically bare bones setup. react-angular, all- all these options down here kind of set up- pre-set up a- an application for you, but we'll- we'll start with nothing and then add what we want here. Let's see, enable distributed caching. We'll say no for now, but later on, we're going to add this in. All right, oh, it failed because I already have that folder, so I'm going to delete that folder. Integrated, we want apps, and no to that. Awesome. So, if you're new to NX, you can go through this tutorial, but we won't do that right now. And let me switch over to a separate VS Code window. Let's see, is this font big enough? Let me try that. Okay, so this is what it generates. You got an app, some library, you got an apps and libs folder, you got nx.json, which we'll- we'll dig into a little bit later, and in your package json, you have just nx and neural workspace. Okay. Okay. So, that's just the initial setup. Now, let me go back to my slides here, and we have a little bit more- a little bit more talking to do. Okay, so these first two slides are just a little bit of a demo. Okay, so nx, the core of nx manages your- like the caching and the dependency graph, and kind of how those- those packages are managed. So, we'll go through that in a little bit later. Okay, so, we have a lot of slides here, and we have a little bit more- a little bit more talking to do. Okay, so nx manages your- like the caching and the dependency graph, and kind of how those- those projects depend on each other. What plugins do, plugins add on another layer on top of that. They're- they're optional, but they can- they can manage your build system for you. They- they help manage automatically upgrading things, and they give you some of the- the repetitive development processes. So, in order to see what- what plugins you have, and what plugins are available to you, you can run nx list, and that will list out all the plugins that are installed, and also a bunch of community plugins, and- and nx official plugins that you can- you could add if you want to. So, to add a plugin, you just install it with npm or with yarn, and then- and then it's available to you. Now, so for generators, semantics is not the right word here, but for- for generators, you can- you run nx generate, and then you give it the plugin name, and then a colon, and then the generator name, and then whatever options you have. So, for the- the angular plugin, you do nxg for generate, at narrow slash angular, and then the generator name is app for application, and then the name of the application is one of the options here. You can also run generators using nx console, which is a vs code plugin. You- there's also a nx console for intelliJ. I think they even have a vim version. Vim is not a- not as fully featured as this, because obviously vim has no UI, but there's a- like IntelliSense kind of features that- that come with they come from vim. So, there's- there's code generation for, you know, a bunch of different frameworks for- for back-end frameworks, like- like Node, and Nest, and Next, and for tooling, like TSLint, and Prettier, and testing tools like Protractor, Jest, and cypress. You can also create your own custom generators. You make a plugin inside of your repo, and then set up generators that are specific to your organization. So, you can either add on to, like, build off of the- the nx provided generators from a- from a plugin, or you can, you know, build up your own generators from scratch, saying, hey, create this file, and- and move these- this code around in this way, and kind of- instead of having a to-do list in a readme file that's, like, 10 steps long, you just have a- a generator that says, okay, enter in the name of the new component you're making, and- or the new route you're making, and then everything gets set up the way it's- the way it's supposed to. All right, so it's not an angular app, but that's fine. So, step two, lab two. We're going to make a- a Node api here. So, when this is done, the file structure will look like this. We'll have a- an api app underneath apps. All right, so let's do- let's see if I have enough screen space to do this side by side. So, if I run nx dash dash version, what's going on here? Let me run npm install. Let's see what's- see what's happening here. Okay. So, the latest- Node 18 should work, but I'm- I'm having issues with Node 18 right now. Let me try- let me try 16, or it could be my- could be my machine is having weird issues. Let's try this. Nope, what is going on here? No such file or directory. Okay, I was doing this yesterday. This is the curse of the demo. Everything fails when you run the demo. Let me try restarting the terminal. Restarting the terminal. Okay, there we go. Good. All right. Something was weird with my terminal, but now it's- now it's better. So, I'm using Node 18, but you can use whatever version you want. Yes, good question. So, Nayakunin asks, do custom generators show up in the- the NX console? And the answer is yes. NX console. So, and we'll- we'll get to this a little bit later, but- so, you click on generate here, and this looks through your- and this looks through your- your repo and finds all the generators. So, in this case, we only have normal workspace installed. Later, when we add other things, there'll be- there'll be other- other things in this list. And if you add your own custom generator, it'll also show up in this list. So, you know, whatever options you set up for your- for your generator will show up here as fields that you can fill in, and then you click run, and then it shows up. Is there a new version? Yes. Okay, cool. All right, so, npx nx, we do this. Okay, so let's do nx list to see what plugins we have. And this is- generates a whole bunch of code here. So, I'm going to do nx list. What is the- is it cat, or is it- Is that it? Nope. Nx list- less. Pipe it to less. There we go. So, this is just the top here. So, it shows you what's- what's installed. Just nx is installed. And so, we've got all of these plugins available to us. So, these are the official nx plugins. And then there's a whole list of community plugins with a short description for each of them. And there's- there's a lot here. So, what we want to add is the- the node- the node plugin. So, we'll do npmi-d, narwhal node, and we'll also add express. Because we'll use express. So, this is steps two- three and four here. All right. So, now we're going to go into step five. And we will use the narwhal node plugin to generate a node app called api. So, we're going to do- use that generator syntax nxg at narwhal node, and then colon, and then the- the- the app- the generator we're going to use is called app or application. And then we give it a name of api. So, if we wanted to use the- the nx console for this, so that's- that's what the- the command line would be. So, I can use nx console here and run generate. And so now, instead of just having narwhal workspace, we also have narwhal node and the dependencies narwhal node, the linter and javascript. So, narwhal node, we create an application here. And, so there's a bunch of options we can pick from. All we want right now is to set the name to api. And it does a dry run of this. And so, let's do- click run here. And it creates a bunch of files. It updates our package json. And updates nx. So, let's- let's look at the changes that it made. So, in package json, it updated- I just formatted that, I guess. It added- added some dev dependencies to set up node for us. Added axios and tslib. Added, you know, eslint for- for linting, some prettier for formatting, jest. So, all of these set up for us. Created a- an api application. With some configuration options and an end-to-end application to- to test- to do run end-to-end tests on- on this on this node api. Okay. main.ts is kind of our entry point here. And so, project.json here is the configuration that- that controls the- what build looks like and what serve looks like. And lint and test. And so, we can see here in nx console here, if we look under api here, we've got- we can run build, serve, lint, and test. So, if we ran build here, it builds it. And if we run serve, that launches it in kind of watch mode. And we can lint and test it as well. All right, so let's- let me go back to the- back to the lab here. All right, so we're going to update the main.ts file to add some endpoints here. So- so, we're not- we're not really doing- this is not a node workshop. So, we're just going to copy and paste the code here. So, this is, you know, using Express and setting that up for us. And here we did it. Okay, so now to serve the application, we can run build.txt. And okay, so now to serve the application, we can run- so, we can either use the nx console or we can just run nx serve api. Oh. Okay, there's an error in the code that I copied. Main.ts, I think. There's another file that needs to be- Yeah, there's another file I need to add and I want to see where I added it. Here it is, this one. Games.repository.ts. So, underneath us, I need to make a new folder, app, and there's new file, games.repository. Yes, paste that in and save it. Then I'll rerun the serve. Okay, so now that's good. Now, if we go to localhost333.api slash games. Games. Sorry, let me move this over here. This is the JSON that we expect to see for the api. All right. So, this is the excitement of back-end development. You see some JSON and you're happy. But the front-end developers would be very happy to see that JSON coming through. Awesome. So, we will go back. Okay, so that's working and so let's commit our changes. So, we'll call this lab2 and that's working. Okay, so what we did here is we created a whole new application and it took us very little time. Can I drag this over? There we go. It took us very little time just to spin that up. So, we didn't have to worry about whatever's in here. We didn't have to worry about setting up tsconfig or any of that stuff. And, you know, we could spin up five more applications in another five minutes if we wanted to. So, we're worrying about the content of our code rather than about all the configuration settings. All right. So, let's go on to the next slides. Questions about that real quick. Let me look in Discord. All right. Not seeing questions. That's good. Do we still have 32 people? So, we have, yeah, about the same number. Great. Cool. All right. We will go to back to keynote here. All right. So, let's talk about executors. So, I briefly showed you the project.json file. So, each project inside of your repo will have a project.json file. So, this slide is incorrect. But this structure is correct. So, basically, underneath your projects, you're going to define build, serve, lint, and test, or whatever other name that you want. So, if you have, like, some documentation generation, you could have a doc target. So, each target test, you're going to define a doc target. Each target test has an executor defined for it. So, if it's a plugin, then it has, you know, narwhal jest and then the executor name. So, narwhal jest is the plugin name and then colon the executor name, in this case, which is just jest. So, we have let me quickly show you. So, in our case, we've got our build and then the executor. So, narwhal es build. So, it's using es build and then the es build executor. If you wanted to, you could use there's a let's see I think there's narwhal js tsc. So, if all you're wanting to do is to compile typescript into javascript, you could run the tsc or if you wanted to switch out to swc, you could do that. And then there are options for each of those. So, the options will obviously change for each executor to set them up in different ways. These right now, these are the options for es build. But you can fairly easily switch out which executor you're using to do things like building or serving or testing or whatever. So, if we go back to here. All right. So, that's executors. So, to run an executor, you can do nx run and then the project name, which is api and then the target, which would be serve or build and then whatever options come afterwards. So, like nx run my app serve or for shorthand, you could take the target name and put it in front. So, nx serve my app or nx serve api or nx build api and then the options come afterwards. So, this works for whatever your target name is and whatever your application name is. Same thing, you could also run it with nx console and it'll give you all the options as, you know, fillable fields in here. Okay. So, let's try that out. If we go here on lab 3 here. So, we've got executors. So, we want to build the app. So, if the if serving the app was nx serve. So, to serve the app, click here. There we go. All right. So, serving the app was nx serve api. So, to build the app, we'll do nx build api. Since I've already done it from the command line, I'm going to try doing it from nx console just so you see that. So, I can either click here and just click like run here. And so, that'll just run it without giving me the form. But I'm going to go down to build here and do build api. And this gives me a bunch of options here. So, these are already pre-filled in. So, these are the values that are in the project.json file. So, the main.ts file is here. So, that's what's pre-filled in here. So, it gives me the default that's filled in in the project.json file. But if you want to change it, you can change it here and then run it somewhere else. Looks like this looks like an nx console error here. Interesting. But I'm going to try and run this. And so, it built it and it put it in the output path that we defined here. So, dist apps api. So, dist apps api is here. And so, this is our output. All right. So, let's see. So, we built it. So, we put stuff in the dist folder. Let's look under apps api projects at json. And it has an execute option that is there. And we want to change this to be minified. So, to bundle everything up. So, let's instead of saying bundle false, let's say bundle true and see what that does to the output. So, if I run nx build api again. So, now if we look under dist here. So, now instead of having that folder underneath here that had the games repository, everything is built into one JS file. So, this has let's see what's the size of this. Oh, I don't know. 23,000 lines of code in here. Whereas if I went to project JSON and set this to false, run this again. Run this again. And here, this main.js file is only 36 lines of code. And then there's the games repository. And then that's it. So, there's other stuff that is like the dependencies don't get bundled in. So, that's the difference. So, you can set that in the project file or you can also set it in the from the command line. You can do dash dash bundle equals true here. And that will bundle it into a single file like this. Okay? So, that's just how you can set whatever flags you want in your in your executor. You can also get let's see. Okay. Looks like autocomplete is not working right now for the next console. That's fine. No. Yeah. Okay. So, let's look. So, we've set the bundle to true. Instruct the executor to bundle all the TS files in a single JS file, which we did. So, the serve target uses the build target. So, this build has options here. So, let's close this down. So, serve target down here has a build target of api build. So, basically what this is doing is saying first build this application using whatever is defined up here and then run node on the output of that. So, that's all serve is doing. So, if you wanted to change this and do so, you can have a development version and production version. So, this just runs the build with production configuration, which is set up here. And then this serve target, serve with development configuration, runs the build for development and then runs node on it. So, if you want to set the NX build api, if you want to set the configuration to production manually, you can do it like that. Or you can set it to development this way. Maybe it's configure. Sorry. I think you have to type out configuration all the way. There we go. So, that ran it with development. So, the default configuration is development here. And for build, the default configuration is production. So, you can change that. These names are totally arbitrary. So, you can set these to whatever names you want them to be. So, that's how you can set up different kind of settings. So, every option underneath production is the same. So, all these options can be overwritten in these configuration files. So, what this does is it takes everything under options and keeps them the same except for whatever's specified here. So, this just overwrites this ES build option. So, just overwrites source maps to be false instead of true. But if you needed to change something else, you could change something else. So, Janik is asking whether you should use the global version or the using NPX. I like to use just NX directly. If you use NPX NX, that's fine as well. NX will automatically use whatever version is set in your package JSON. So, if I have a global NX of so, right now, 1592 is what's installed locally. And I'm sure my global one is not that. So, if I CD out of here and I do NX dash V, it doesn't even tell me. But if I do NX dash V inside of BG hoard, it is maybe I have to do NX dash H3. So, here inside of BG hoard is B15.9.2. If I CD out of here, NX dash H version, it still doesn't like it. All right. Well, yeah. So, it says it can't find the global version, which is weird because I'm definitely using NX globally. But so, NX will just use whatever's run locally, whatever's there inside of this folder. Let's all right. So, let's do a I'm going to go through the slides and then get little tech. I will maybe have you share your screen while people work on Lab 4 because I'm curious why it's not working for you. So, let me do some slides for Lab 4. And then I will share my screen. Okay. So, so far, all we've done is created an application. We're an hour in. Okay. We're good. So, far, we just created an application. Next, we're going to create a library. So, the difference between an application and a library is a libraries are designed to be used by applications or other libraries. They're not designed to be deployed. They could be published to npm to be used outside the repo. But besides that, the besides that, it's they're expected to be used by the code is expected to be used rather than deployed directly. Whereas applications are, you can deploy them to host them somewhere or you can run them directly. That's what an application is. So, typically, you can have libraries that are that are features, like a route in a frontend application or they're UI focused or data focused or just kind of like lower level utility libraries. So, you can you can make libraries as granular as you as you want to. But NX operates on the the the library level or the application level for each project. If if you want to use more of the NX caching functionality, then it helps to split things out into more more libraries. So, libraries can have basically whatever directory structure you want. You can have a folder. You can group them within folders. They don't have to be inside the libs folder, but that's that's what the default is for this this workshop set up. So, you can have them at the root level if you want to, or you can rename the libs folder to something else. You can call them packages or or libraries or whatever whatever name you want there. Typically, we see for large organizations, you we have a for each application, you typically have a folder inside the inside the libs folder. So, libraries that are specific to that application would go in that that folder. So, you'd have like a lib slash api folder and then a lib slash, I don't know, front end or lib slash store if you had a store application like a front end application. And then you'd have a lib shared folder that that libraries that could be used across multiple applications would live in. So, basically, that whole structure is pretty much up to you what makes sense for your code base. So, generally speaking, the more you split up, split code up into new libraries, the more caching will help you and the more indexes infrastructure will help you. But there is a cost to creating new library. It's another you're the code is being separated out into different locations. So, you have to kind of know you want to group code together that that makes sense to be together. But if it's it's the same the same pros and cons that that, you know, splitting code into separate folders or splitting code into separate files or separate components. It's the same thing with libraries. You can definitely go overboard on one side or the other. Just, you know, and you can always change after the fact. If you if you have a library that's too big, you can split it up. If you have too many libraries, you can you can combine them back together. So, your dependency graph is on a project basis. So, each each library or application is a node in the dependency graph. And that's that's that's what we have for libraries. All right. So, let me go back and answer some of these questions in Discord here. So, I saw Tune is asking, can NX call package JSON scripts? Yes. Yes, you can. So, this this workshop is showing you an integrated style repo that's using project JSON. But if you were to have let me just show you real quick here. Let's see. File. JSON. And package JSON. It's fine. I think I want to call this. I think it'll get confused if the name doesn't match. So, scripts. So, if you if you this so, project JSON is optional. You can just have a package JSON instead of a project.JSON. And let's see. Or you could have both. So, let's say call this hello. And we'll say echo hello world. Let's do hello from package JSON. So, if I run NX hello, because because hello is the name of of the script here. NX hello and then api is the name of the of the project. It runs hello from package JSON. So, whatever you put in here, it'll run for you. So, yes. And then there was another question. Yeah. Get little tech. Would you like to share your screen and show us what's going on with your VS code? For everyone else, feel free to get started on the lab 4, I believe is what we're on. Next one. Yeah. Lab 4. We're creating a oh, lab 4 is actually if you get stuck. So, we'll go on this. Okay. So, you click run. What happens when you click run? Nothing is showing up. Yeah, exactly. It kind of doesn't seem to work. But maybe it doesn't. Try clicking on build in the lower down under generate and run target. Okay. Click on super node. Yeah. And then click on run here. Does that work? It doesn't work. But it actually run from from from the terminal. Okay. I don't know. Yeah. I can figure it out later. Yeah. Okay. Yes. Okay. And Janek is asking if I'm building a front end app, what's the best practice for structuring adding new features? Yeah. So, typically, you want to if you're adding a like a page or part of a page, you want that to be a library, not an application. And and then you'd use that library within the application. So, let me let me go over lab 4 here. Lab 4 is digging into migrations. And also how you need if you need to jump to a particular if you get stuck on a particular section of the of the of the workshop, you can jump to a jump to that step. So, one of the things that that edX plugins give you is the ability to to automatically update your code, update your dependencies, and then update your code to account to account for the to account for the the breaking changes that that might have happened. So, what we're going to do is we're going to run we're going to add an edX workshop node, an old version of it. And then we're going to run yeah, and then we're going to try migrating. So, let's do this. MPMI-E. Actually, at narwhal. Okay, so this is an old version of edX workshop node. And so, we're going to run this. So, this could be any any plugin that we have. So, at narwhal at narwhal slash node or at narwhal slash react or whatever whatever plugin you're using. Let me delete this package. Jason here. And okay, so we've added this. Okay, and then we're going to commit our changes here, just to make sure we've got a clean a clean setup here. And so, then we're going to migrate to the latest version of an edX workshop node. So, edX has the edX migrate command. At narwhal edX. So, if you just do edX migrate latest, that will migrate edX itself and any plugins that are installed. So, if I ran that, that'll do everything basically. And so, let's see what it updated. So, all of these are already up to date. And all it did is it formatted it. Let's see, edX workshop node is there. So, let's do edX migrate narwhal edX workshop node. Let's see. That should find the 1.0.0 version. That checked that one. Let's see if I manually set that up. Okay, there we go. So, it found it. Okay. I think part of the issue there, why that wasn't working the way I expected is when I set up the workshop, I published 1.0 first and then 0.0.1 later. So, normally, you just say edX migrate and it'll find the latest version. So, what that did is it updated huh why did that not update that okay so typically what it'll do is it'll update this oh I see it did update it I just didn't uh there we go good it did what it was supposed to do so it updated this to 1.0 and it also created this migrations.json file and so these are all the code generators that um that are needed to move up to that that version the next version and so we're kind of um using a kind of a Frankenstein version of this so typically this would be like modifying your code to make any any breaking to fix any breaking changes basically for that for that dependency um but in our case what this is doing is it's um each of these generators is completing the step for each lab so this is completing lab one this is complete lab two three lab four is what we're doing now so there's nothing to do there um and then this will skip you up to lab five lab three and five and all the way up to lab 13 here so if we were to run all of this um you can run nx migrate dash dash run migrations so that runs all of these um steps set up oh no so that does not it's got some old code in there why does it have old code in there and our own live debugging I should not need that is it there's no anchor involved there. Going to run migrations for that. Let me just run MPMI. Okay. There's something wrong with my migration so I'm just going to run, I'm just going to install that. There's no reason that that should be there. But just to move forward. We'll run this. Okay. Okay. So this ran through all the steps up to lab eight where it stopped. I got stuck on something. But you can see that here, it made a bunch of changes, adding some apps and adding some libraries in here so this is all stuff we haven't gotten to yet. So if you want to just run up to up to lab three here. I'm just going to comment that out, even though it's not valid JSON. Run this again. So this will just run for lab one, two and three, and get us back to the place where we're supposed to be. Let me remove those. Alright, NX console has a stale. I'm going to run the remove command. NX g. Rm CLI bash. And I removed the interface library. Okay. And we're back. So this set us up up to lab three. If we want to go to lab five, we can comment this out. So we can either do it this way. Or there is a generator that comes with the, with the workshop plugin here and next workshop. Where is it and next workshop. No, to complete labs. And I want to complete lab five. So if I complete lab five. You can do a range or you can complete a single lab and run that. And what that does is that updates my migrations as JSON file to just have completing lab five here. And then I can run. And next run migrations here. And run that and that will just complete lab five. So the idea here is that your. This helps you to to keep your repo up to date. Basically, the, the library maintainer can write these migration scripts. So when they know that they're making a breaking change, they can say here, anyone who's using this library to run this migration script and and you can update without without needing to know all the nitty gritty details of the different api changes. That are happening. So this created a new, a new library that we're going to make in the next lab. Okay. Go back to here. So, for our case, the. So for this particular scenario, this this NX workshop node. These migrations scripts. The. The first complete lab one is going to reset everything, delete all your code, basically, and then. So if you have code that you don't want to update, you don't want to be deleted, then don't run these scripts. Or you could try just running the like this, the single next lab, and that'll just try to add on code. And always, if you don't like what a generator has done, just make sure you commit before you run it. And then you can always revert it here with your get history. That's always a good practice when you're running a generator. Make sure you're you have a clean get history and then you see what it did. And then you can undo it if you need to. Okay. All right. So let's move on. Let's do we'll do one more lab and then we'll take a break. Think about it. Maybe a 10 minute break. Okay, so let's do the. Let's see, do we have that for. I'm curious if there are any. No. Okay. It looks it looks like we're doing slides. Good. We are done with slides. Let's just do the next the next lab. Okay, so far, we've just made applications. Next, we're going to make a library. So let's say we have for our api app. We have some authentication that we need to do, and we want that to be kind of reusable logic. So this this off whatever authentication logic we're doing, we're going to keep that inside of a library. And and then we're going to. And then we're going to use that code in the application. So so this is a good time to answer Janik's question. The does does an X differentiate between lives and apps? The only difference. Basically, basically, no. Basically, they're the same thing. The the main the main difference is for where the users of your app of your of your code base. So it's helpful to be able to see this. This is like the entry point for our for the code base. Like if you want to understand from a high level what's going on in this code base, you look in the apps folder and you see everything that can be deployed. And that's like a high level understanding what's going on. You can make libraries that have a serve target. You can make obviously you can make have a build target on libraries. You can even have a deploy target on on libraries so that you can do everything you can do with an application. You can also do with libraries, but just typically applications are at the at the top of your. I haven't even shown this yet. Let me let me show the next graph here. Typically, libraries are at the top of your of your your dependency graph. Let's zoom out a little bit. So api is here at the top and then the library api that we're about to create is at the bottom. These things will make later. But. The you know, even in the in the graph here, they have the same symbol. So basically, you can be you can treat them as the same thing. Yes, basically, apps are have entry points. OK. All right. So let's move forward. So to to make a plain javascript library, we're going to use the normal JS package. Now, JS plug in. And that was already installed because we installed the the neural node plug in and neural node depends on neural JS. So you should already have neural JS available for you. Let me get rid of these. Things those don't need to be there. And the. So we're going to make a new library and we're going to call it off. So to generate it, we're going to use the generate command again. And next, G. So the plug in we're going to use is neural JS. So to know which which plug in to use. So I'm going to run to generate here. So there's two different kinds of libraries that are available to us. You can do JS or node. JS is for kind of low level javascript that could be used in a node context or could be used in a front end context. Whereas you want to use node for things that are node specific. So node things can use javascript. react could use javascript. angular could use javascript. Any any job, anything that's written with javascript to use a plain javascript library, whereas node would be specific for node. So to make a javascript library, we'd use an XG at our walls as JS and we call it going to use the library generator and we're going to call the library off. So I'm going to type in off here. What unit test test runner do you want to use? I guess we'll use just but you can use V test if you wanted to. What bundler will say, I guess, TSC. But you could play around and use whatever you want there. Why so serious? You mentioned that your migration that Jason is not created. That means you need to run. You need to run an X migrate. Command this X migrate. You can either say migrate latest or migrate X migrate at Narwhal X workshop node at one point oh point oh. That will create your migrations that Jason file. OK, so we've made the the. Off folder. Oh, you know what? I made a mistake here. We've actually already created it. And but we want it to be underneath the lives api folder. So let's actually. I'm going to show you how to delete a library after you've made it so you could you could just delete this folder. But the problem with that is that. And X also created another entry in here and potentially has modified some other configuration files with that with that library. So if you just delete the folder, you'll have extraneous configuration other places. So in order to remove it, there is a remove generator under Narwhal workspace. R.M. You can do R.M. or do remove. But you can also just omit the plugin completely. And if it if there's no. So you couldn't do this. Well, you could do this live if you do NXT live. It lets you pick which one which library you want. So but since there's only one remove, remove generator, it knows exactly which one you want. But the problem is that we need to tell it which which project we want to remove. So NXT and generate remove. So we're going to do remove off because I put it in the wrong spot. What I want to do is I wanted to put it here under api. So if we do NXT live. And we want to set the dash dash directory flag here. Set it to api and the name is going to be off. Or the other thing you could do is you could do api slash off like that. And then it knows that the directory is api and it asks you which kind of library you want. I'll say JS and all of this. And then it sees. Oh, wait, it already exists. So we don't we don't need to create it because I already already made it with the migration. When I ran the migration script with complete lab five, it already created for me. So. We'll leave that there. And then this is copying into api off. I'll show you what that looks like. So this is what was copied over. And then to use it in your api off endpoint. So back up here on your api source main TS here. So this off endpoint here. It's created. So this has an error because we need to update our we need to rerun the typescript, restart the typescript server. So every time this TS config base gets updated, these paths get updated. The typescript server doesn't notice that it's that it's been updated. So you have to run restart typescript server. I just did a command ship P and restart and this shut up and that that fix that that runs quickly there. So this is how it it imports the the do off function from our library. So it's defined here under api off and it's exported here on the index that TS. It says, OK, everything that's in api off. I'm exporting and saying that this is available to be used. If I were to comment this out. Then this would this would fail because it's saying, I can't I can't export anything. So this is this is your your public api for your. For your library. OK, so what's next? And then let's let's launch the launch project. OK. So let's do if if ever an X is confused about something, you can do an X reset, which is basically tells it to recalculate. OK, let's do this. I ran an X graph. And so here we have our api application and the api off library. So this connection here, it says this api depends on api off because of the the contents of source main TS. Because of in here, because of this import line here, that's that's where that dependency line comes from. This is very useful, especially in a larger repo where you don't know why there's a dependency line being drawn. It can help you track down, OK. You know, why is there a circuit dependency? Why is this depending on that which is depending back on itself again? You can figure out what what files are causing that to happen. OK. And so I did not. I did not change any of like Project Jason files or I did not tell an X that that api is depending on on the off library. All I did was write my code. And so if somebody else later on comes in and removes this line of code, then. Then we run an X graph again. And. OK. I have to do a reset here. So there's some issues, I think. And let's refresh this. There we go. And so now I see is, oh, yes, they're not connected anymore because somebody's updated the code to to break that line. So we'll put that back. And let's commit this as lab five. All right. So we finished lab one through five. We'll be starting a lab six here and then we'll we'll dig in. So six and seven is finishing out the code. Code. Portion and then eight through 13 is focusing more on kind of like the architecture kind of structure of the of the repo. Let's see. Andrax saying. Yes. So, Andrax, we will we will answer your question in in lab eight. So we'll get there. All right. So let's get started on lab six here. And the answer is yes. So lab six, we're going to add a node CLI. So. Basically, we're making another another node application. Keep the. Keep the instructions on the side here so we can create a new node app. Call it CLI and XG app CLI. So I'm not specifying the plug in. So it should ask me. I guess. So there's only one app generator right now in our in our plugins. So what framework do you want to use? I'm going to say none because I just want to be a CLI. And so it sets up with the name CLI. Let's update the main TS file with the. You're copying it. So we're going to now we have two main TS files so that we want the CLI one. That. And so this is. Not using. Any other libraries. So if we were to run an X graph again. Fresh this. So you notice the CLI doesn't depend on anything else. We just have an end to end application and then the CLI itself. And that should be. Yeah, that's that's not sick. That's lab six. Awesome. Let's move on and lab. We skipped. All right. That link is wrong. We want to go to lab seven, not lab eight. OK, lab seven. Let me commit this. That was lab six. Very quick lab six. If we wanted to. Let's let me just show off real quick to run the. The main TS file. We would do an X serve would run it. So if I do. That next serve CLI. Oh, OK. It's trying to make a. Trying to make a request to localhost three three three three. But that's the api is not running. So if I just serve the api and then in a separate. Separate terminal to serve CLI. So here we go. So it listed out it made it made a api call and then listed out the the name and description here. So it did it did work the way we wanted to. It looks like the slash end isn't working in the console here. So if if it so it's listing all the games here. So that is working. Close enough for a demo. Let's move on to lab seven here. So now we want to share a library between two different applications. So sometimes you want to have some kind of connection between the front end and a back end application. So the CLI is our is our front end. And when that when that that interface changes, we want both the the front end and the back end to make sure they're matching that that interface. So we're going to make a a typescript type to to define what a game is. And both the CLI and the and the api will use that typescript type. And so that way they can they can be kept in sync. So whenever that that contract changes, they're both they're both in sync. So that's what this this lab seven is all about. So. So stop serving it. So we're going to make a new javascript library. We'll call it util interface. And we'll make sure to stick it underneath the api folder. And then later, we'll we'll move it around to to move it somewhere else. So let's do so. And X generate library. We're going to call it api and util interface. So here's my shorthand to make sure to stick it underneath the api folder here. OK. And then because I didn't specify the plug in, I'm going to ask me which one I want. So I want to use JS, not node. Then I don't care really. Which of those. So under apps, we've got that. So now under libs, we've got libs api off and lives api util interface. And now let's see what's next. OK, so we make the games game interface here. OK, my link is not correct. Source api interface. Is it slash maybe? No. OK. I have to go find it. Samples of seven. This is the one api util interface. If someone wants to get bonus points, they can go in and fix the broken links in my code here. OK, so update that. And so we've got game being exported for you, api util interface. OK, so we made that we want to import it in the api repository file. So basically, we want to import it with this and then use it on this line here. Copy that import statement. So the games repository TS, that's what I wanted. This. So this is under the api. And then we're going to give this games objects, the type of game array. So now I'm I'm importing from the. The library that I just created and then using the type that is defined there. And then we're going to build it and make sure there are no errors. So next build api. OK, good. So notice here when I ran an X build api, it's first it was building the the util interface library because util interface also has a build. A build test to find. So it does the build for this first and then it does the build for the api itself. And X build api is that and then if I run it again, it read that from cache and I read this one from cache. So it's you know, it took no time at all, whereas this other one took, I guess, two seconds. So it's not doesn't make big difference for this small example, but for a larger, larger app, it'll make make a huge difference. Your your cache version will still come back and in about forty nine milliseconds, whereas, you know, the the real build could take, you know, two minutes up to 30 minutes, depending on what you're doing. OK, so we built that and then let's do the let's check out the project graph here. Here we go. Let's refresh this. OK, so now we've got this this new library we created and APIs depending on it. That's good. Now, what comes next? Let's let's commit everything. OK, so we'll commit under lab. Lab seven. OK, so see a CLA makes calls the api, but it's using type any. So let's use the type in the in our CLI as well as the. As well as the api. But the issue is so we could we could just use it. But the the the folder that I send kind of doesn't make sense. The idea here is that everything in this api folder is only used by the api application. So we want to move this util interface library up a level. So it's directly underneath lips instead of underneath the api folder. So if I were to just move this folder. There would be there would be problems with that because a lot of the configuration options wouldn't be updated correctly. So we want to use the the move generator to move our our library. So let's do this. So you can so I can do an XG move or just envy. And I'm just going to run it like that. So it's says it's running that. So it needs a project name. Let's actually do this with an X console so I can figure out what I'm doing. So envy here. And X console has a. Automatic dry run feature. So I want to move this util interface. And I want to move it instead of api slash util interface. See. OK, we got the issue was that this this drop down here had a stale list of projects, and so I had to go into an X console and click refresh here and then reopen the reopen generate tab. So typically you don't have to do a lot of this refreshing stuff because you're not making new projects every 30 seconds. But in this workshop, we are so so api util interface and then we want to move it to util interface and then it's running this dry run here. And so you can see the. The files that it's creating. So it's deleting that and moving it. So api slash and deleting that and moving up to upload. So that's that looks like what we want. If we wanted to, we could also change the name here. So just like the. The Linux envy. That's that's how you change name. So that would make it, you know, util interface change name here or you can move it into a. A different folder here so you can you can do whatever you want. So if you're stressing out about out about what you're naming the library or where you're putting it, it's don't don't worry about it. You can always move it later. So this this is what we want. We can take it from the api folder and move it up a level. So we'll run that. And now we can see that the changes that were made. OK, so it moved it from. The APAC folder now it's down here. At the top level, which is good. And now we're going to start using it in the. In the sea life. So we're going to take this. And then I have some formatting issues here. That's OK. Here. And in the main TS of the sea ally. So let's see. So this Val is not an any we're going to call it game. And we need to import game. Here. OK. So now if we go back to our instructions. Let's look at the the games repository. Yes. If you notice in the. This this import path was automatically updated for us. It used to be api slash and that got updated when we moved our our utility interface. Also, the the path here got got updated. So that that got taken care of for you for us by the. By the move generator. Hey, now if I run. OK, I'm going to fix this line because this is. Here. Right click. Three dots. Nope. Nope, that's not it. Let's see if I can find. No, that. Line panel. Position. But what was I doing? Run the next graph. And I refresh this. OK, so now the sea ally and the api. All right. I have to do the next reset here again. So this does not typically do this. So is I think there's an issue with the latest version of X. But so now the api is depending on the util interface and the sea allies also depending on it. So if this ever changes, then both of those these need to be updated to reflect the new the new interface, which can solve a whole bunch of. Of issues. That you can find in production. All right. We got that. Anything else in this lab? No, that's it. That is lab seven. I'm going to commit this lab seven. And now let's check out lab eight. So lab eight. We do have some slides for it. OK, so let's say you've got a set up like this. You've got multiple different frameworks being used in your repo. So you have angular. You've got remix. You've got a you know, I'm not sure what maybe this is nest. I'm not sure what that syntax is, but some some back end api. And you've got libraries that are all they're all using. What we want is to have some kind of rules saying that the the remix code can only use remix code and plain JS code at the bottom here. angular code uses angular code and this stuff. But you want to have some structure to this and have have rules about what which libraries can depend on other libraries. That's what that's a module boundaries take care of for us. So if the api starts depending on something in remix, we want to be able to say, hey, don't do that. Or if the plain JS starts depending on, you know, api specific stuff, that's that's a no no. Those are apps, those are libraries. So these are like our named apps. So this is an admin app. This is a store app. This is an api app. And then we've got shared stuff at the bottom. So apps can only load libs, libs can only load libs. So we want to set these when I set these rules, so whatever tagging system we have, we want to be able to define for our repository, like group things by type or by name or by, you know, different dimensions. And then be able to set up rules about what those constraints are, about which things can depend on other things. So there's a lint rule called enforce module boundaries that comes with an X. So you can say things like if this if a library has this tag of type colon util, which is this can be whatever, whatever string you want in here. You say type util can only depend on libs that also have that type util tag, and they cannot depend on anything with react, like any dependency like npm dependency or anything with react or anything with the angular namespace. So that's what this this lint rules is for enforce module boundaries. There's three different kind of subcategories within this lint rule, you can say only depend on libs with with a specific tag. You can do the inverse of it cannot depend on libs with a certain tag. And you can do have rules about external imports, which is like npm dependencies, things outside of the repository that they're not allowed to depend on. You can have a rule about banning transitive dependencies. Say if you're using if you're if you're depending on, I don't know, Express, which is using some other dependencies like Chocodar or something, and then you're using Chocodar inside of your own inside of your application. It could work, it could build and compile and work correctly. But then if if Express stops using that dependency, then you'll end up with a You end up with a broken application without realizing what happened. Let's see. So you can you can say that don't don't do that. You can check for nested external imports. I'm not sure exactly what that's about. So if you want to. By default, it doesn't let you do a circular self dependency. So if you depend on a library that depends on the first library, it'll it'll stop you from doing that. But if you if you already have those and you're trying to adopt NX, you can say, okay, just allow that for now until I figure out how to solve that issue. Okay, so that is the. Those are the slides for module boundaries. Let me, let me look back here. Yeah, so Janik if you're if you're seeing things that Yeah, if you're seeing things that that shouldn't be there. You can run NX reset to to reset that But it looks like you figured it out anyway. Yeah, so typically if things are If things are already libraries, then it'll keep it in the library folder. There is a A property here under project JSON. And that just determines which folder goes in when you're using the integrated setup, but So here this, this is the project library that's where I next knew. Well, that's super nice to go. By the way, as I remember once a minute goes into my file. You can sort of control a web app or something in that way. Yes, you can Okay, so let's do the next lab where we set up some of these module boundaries. So to set up tags, we'll go into the project.json file for each project. And we're going to set up some scope tags and some type tags. So for scope, we're going to set up a scope of CLI, a scope of api and a scope of shared. And then for type, we're going to have type of a type could be application type app. And then for libraries, we can do type. Type util. Let's see. So this is going to be a type util and then off. What should we call this, we can give this a type of Probably data access. So in here in this tag here, we can do this, you know, whatever strings we want in here. So I'm going to give this a scope of api. Give this a type of data access because it's accessing the authentication data. And then this one. This is the util interface library. So this is a scope of shared. And give this a type of util. And then we've got two more projects to update. So this is a scope of CLI. So this is a scope of CLI. And a type of app. This is the api app, so we'll give it a scope. Scope api. Type app. We can do the same thing for the end to end applications. Which we haven't actually used. So not have tags. Okay. If there is a project here that does not actually import. So this end to end application does not actually import any code from the api app. But we want it to. We want to run the end to end tests whenever this api app changes. So you can manually add a line in the NX graph by defining these implicit dependencies here. This is saying just manually saying I want to draw a line where api ETE depends on api. So there are cases where you need to do that. So I'm going to add a tags property here. And so you notice in here we had some autocomplete. Because this is coming from an NX console reading the schema for the project JSON and knowing what properties go in here. So tags. This is, I guess, scope api because it's an api end to end test. And then type. It gives us a type of ETE because it's kind of different from an app. But you can name these whatever you want. Whatever makes sense for you. Basically based on whatever you want your rules to be. That's how you should set up your tags. Scope CLI and type ETE. Okay. Let me actually run the NX graph again. And in the graph, yeah, it actually shows you what the tags are. So if you click on these things, it shows you scope api type ETE. And this one is scope share type UTIL. Okay. So that can be useful. All right. So now I think it's going to have us do the, so we set up all the tags here. Next we're going to define our dependency constraints. So in the root ESLint.rc.json file, we're going to modify this rule and set up some dependency constraints. So I'll copy this one. And then we'll do the same thing. So ESLint.rc.json. Okay. So this is our rule here. NX enforce module boundaries. And dependency constraints. So this default setup here is saying any project can depend on any other project. So we want to be more restrictive than that. So we want to delete that and add in our own rules here. So if it's a scope of CLI, let me minimize this. So if it's scope of CLI, it can depend on CLI and share. Okay. So then let's add some more. If it's a scope of api, then it can depend on other api things and shared. If it's a scope of shared, it can depend on other things that are shared. That's it. Okay. Now we'll set up some rules for types. So if it's a type of app, that's actually, it should be able to depend on, it can depend on anything. So we can just leave that unspecified. Yeah. So we can just do a type, type data access can depend on type data access or type util. And then type util can only depend on type util. All right. So let's just do a sanity check here. And do, so I could do NX lint api to run the lint on one particular project, or I could do NX run many with a target of api. And this by default will run, not target api, target lint. And this will run the lint target on everything in the repo. You can also specify a list of targets with a list of projects with the dash dash project flag, but we want to run it on everything. So we'll do this. Okay. It looks like everything is still happy. So let's see if we can break this lint rule. And make sure that it's actually working. So let's do, let's make a util library use a data access library. So we'll go to the util interface here. And in here, we're going to do add an import. We want to import the do auth. Function. Okay. So it imported it, it found it, which is nice. But now let's run the, rerun the lint rule on, actually, yeah, NX lint util interface. We have an error. It says buildable libraries cannot import or export from non-buildable libraries. Okay, that's a different error than I expected. So that's an issue where this one has a build target, whereas api auth does not have a build target. We'll just, I'll just add something here to skip past that. Okay. Okay, so we added a do nothing build target here. Okay, so this is the error that I was expecting. The project tag with scope shared can only depend on the tag with scope shared. Okay, well, that's not exactly the error that I was expecting, but it also is valid. Because this has util interface is, has type, has scope shared, and api auth has scope api. So that should not be allowed because of that reason. There are a couple, there's another reason, like this rule was also violated, but it found this one first. Okay, so we can do the same thing with, yeah, we could have like the, the, we have the CLI importing the do auth function. And that would also, that would, that would violate this, this rule here. So these, these rules are arbitrary. You can set them up however you want. But this is a powerful feature that can impose some, some structure to your, to your code base. Yeah, okay. So could you disallow express from the CLI? Yes. Let me answer that question first. So if you did, let's see, how would you do that? Yeah, so you do this. So source tag scope CLI. We can add another property on here. I guess it doesn't, it doesn't auto complete. So we want to add a band external imports. I think, is that it? I will look, let me look up the documentation here. is the documentation site. And so I want to go to actually packages. And I'm going to go to packages. And so I want to go to actually packages. And I want to find the linter, linter here. And lint. Yeah, I'll just, I'll just do a search for N-course module. Sure. All right, I'll do a search for band, band external imports. So this is under packages. Oh, here it is. So it's the ESLint plugin Nx package. Okay, so here are the options here. So we can add only depend on libs with tags. That's what we've been using mostly. But we can also do band external imports. So this is an array of imports, of packages. So here, band external imports, so that's correct. And then it's an array. And then I could put in express here. You could also use like a wildcard in there, express star. So now if I go into CLI here. And I want to import from express. Doesn't like it. So it says, hey, don't do that. Not allowed. Yes, so Janik says target names are arbitrary. They are arbitrary except for the build target. There is some extra, extra logic around build. I think that's it. We have a term called buildable library, which just means it has a build target. We also talk about publishable libraries, but I don't think we have any special logic around that. It just refers to libraries that have a published target. So just a shorthand for that. There was another question up here. Okay, so Janik was asking about how do you recommend splitting up an existing application? So for an existing application, you would run. So just to get started, you'd run NX init to just install NX and set up a base NX.json file. And then once you have, say you have a folder in here that you want to turn into a library, what you would do is, so if I wanted this games repository to be its own library, I would do, basically I would NX generate a library and call it, I don't know, repository, maybe games-repository. And then I guess it would be JS, whatever you want it to be. And then you just basically, you have to copy over this code into that newly created library. So then you'd copy that over and then all references to that, you'd have to manually update inside of this app itself. So there's a little bit of manual process involved there. But, there's too much complexity to automatically do that for you. Okay, so we added that, we created this, we did run many. We tested that, we tested that, we tested that. Okay, let's just make sure our linting is all working. NX run many. The other thing we could do, I want to show this off actually. I want to show off the NX affected command. Let me just make sure that the linting is working first. NX run many, target, target equals lint. So one failed, I need to fix the, this. So the VS code hasn't caught up with our new lint rules. So if you want to fix that, you restart the ES lint server. And that now it has the correct squiggles. That's because we changed the rule and it just needed to be updated. Okay, so if I fix that, now our lint should be working. Now you notice how quick that was? That's because most of these things were cached. These five lint targets were already cached, and they just needed to run this one that I just changed. So if I run it again, it's instant again. Okay, so there's that. Now let me commit this, and then I want to show off the NX affected command. I'll just lab eight. So let's say if I were to undo this, I could, instead of doing NX run many to check and see what I need to run, I can run NX affected. This command runs a certain target, target equals lint. So this is saying to run the lint command on any projects that were affected by a particular code change. So right now I've affected this file. So it looks at your git history, and you can set up a base branch that you're comparing against if you want. You can set up the base and the head, but default head is that, and actually I think default head is like your current working setup, and then base is head, I think. Yeah. Head is, I don't know how to say in git what the, whatever is currently in your file system. So if I run this, it ran, let's see, NX run, NX affected. So it ran, so this util interface, so it ran it for a CLI, ran it for api, end to end, and api end to end. So it ran it for five projects, which makes sense because this game's repository is unaffected, and let's see, what else is unaffected? api auth is unaffected. There's no reason for api auth lint to be affected by this code change. Okay, but these other ones, they could potentially be modified by, the lint could fail because I've modified some code here. Okay. So that can significantly speed up your average CI time because of you're not running tests that don't need to run. Okay. All right, so that's lab eight, module boundaries. Okay, we have a half hour left, so let's look at, let's look at what's left and kind of be strategic about what we want to do. Okay, so I think we have time for, let's do a, I'm going to stop sharing my screen. And for the people who have stuck it out so far, you get the power to vote on what we talk about next. So we can either do, work on making your own generators that are local to your repo, or we could work on setting up CI and distributed caching. So we're going to do the, use the same thing, raise hand, let's see, where's the raise hand feature. Thought it'd be a reaction, but there it is. Yeah, under reaction. So you can raise your hand like that. So raise your hand if you would like to do, so the options are generators, custom generators, or CI and distributed caching. So raise your hand if you want to do that. So we're going to do the, CI and distributed caching. So raise your hand if you want to do generators. Custom generators. So I've got two votes for generators. Okay. Three, three votes for generators. You can unmute yourself if you're frantically trying to find the reaction button. The, all right, let's do votes for, for the CI and distributed caching. One, two, three, four. Okay, looks like distributed caching wins it. All right. So if you, if you want to look at the generators, you can do that on your own. That's labs nine and 10, but I'm going to skip to, skip to lab 11. And, okay, so lab 11 here. Let's set up CI. Okay. Okay. So this is setting up distributed caching. Setting up CI. Actually, hold on. Yeah, that's right. Okay. Okay. So, we're on main. We need to commit it. Okay. So we're going to copy this. And we're going to create a workflow CI.yaml. What was all these? That should be under.github. There. New folder..github. And I think it's workflows. And then a new file. CI.yaml. Paste that in. Okay, so what this is doing is it is, whenever there's a PR, we're running npm install. We're checking out the latest. We're running the tests for the CLI and running the tests for the api. Okay. So we do that. And then we commit that to main and push it up. All right, so let me, let me do, let me do, is this 11? I don't remember. Push it to, okay. So we've pushed that up to my, okay, that's working. That's pushed at a CI. Okay, so we're, we're going to open up a new branch. We can check our dash B, modified. And then new branch and update our main.ts, make some change. Let's, let's do actually the CLI main.ts because, here we go. This would actually like something. Okay. So that's a new message that will be displayed on the CLI, the terminal and committer changes and then push it. Okay, changes made. These are really great, great commit messages. It's like the, and you've seen that XKCD comic. So we pushed it up. Okay, and then we're going to go to our repo here, BG. Missed a slash. Okay, so we're going to make a PR here. Great pull request. So we've made that. And then we'll see this unit tests are failing. So let's see. So it's running this, so it's running the test CLI, running the test api. We'll come back and check on that in just a little bit. We're setting that up. Okay, so we want to update this to only use NX affected instead of specifically instead of doing two different jobs here. So yes, thank you. The XKCD link. So I'm going to update the CI that YAML. So instead of test CLI, we're just doing test affected. And instead of doing the test api, we're just doing the test. So we're going to update that. And then we're going to update the CI. So we're going to do NX affected. And instead of running NPX NX test CLI, we're going to do NX affected dash dash target equals test. All affected projects. And then we don't need this anymore. Okay, so that's the change we'll need to make. Let's see if I should make it on main or on in our branch. Okay, so let's double check this. Let's set it past. Interesting. Looks like our unit tests are not very good. So it ran NPX NX test CLI. Oh, no test found. That's why the test pass. Well, good job. Good job. We have succeeded at not running any tests. That's the best kind of testing. So, you know, NX affected is not helpful if you don't actually, you don't actually have any tests. Then it's, you know, you're living in the Wild West at that point. Okay, so let's keep going. Okay, so we made that job. testing I've been running sequentially for each project. You can run them in parallel. So by default, dash dash parallel is set to three. If you want to make that more, you can, you know, change the number or I think you do parallel equals one, make it in sequence. So let me let me commit this and. Okay, so committing that and then we'll see. We'll see what it does over here in just a little bit. Okay, so only those testing. So we can also have it do Lint, ETE and build. So we can add more steps in here. So ideally we'd want to do. That and then do. Lint and build and ETE. I'm not sure if this is in the latest NX, but soon you'll be able to do this. Lint build ETE instead of having four different lines. But right now, I think you have to do this. All targets. Let me just. Let me just. Let me just. Let me just check the targets. Let me just check the this one here. Okay, so this one has updated and. So not target test is failing. Is it failing? Biggest argument main unknown revision or path not in the working tree. Oh, okay. So that issue is because we don't have the main branch checked out in CI. So we need to. Let me see in here. Let me make sure that this is set up correctly. That's right. That's right. Let's check out whatever branch the PR is using. And. So. So this. This is probably not optimized for. You don't you don't want to check out every branch. I think that's what this does. So in your CI, you probably want to do something differently. But. Go back here. And. OK, where is this? Where is this? Where are the github actions? Move that steps. There we go. So for some reason, it wasn't showing up in that. Invalid workflow file. Line 13. OK, so it doesn't like this. Do I need another indent? Maybe. That's good. Let's look at that one. OK, so it likes the file. That's good. So called test CLI. And that's because I never didn't change that name. But there's only one of them, which is what we expect. And it failed because. OK, so it still doesn't like it's still not checking out the main. Main branch here. The base needs to be available to it. All right, well, we have to update that. OK. Before you run NX affected, you need to do this. OK. So I found this from the the docs here. Before github actions. I found this from. It's setting up that setting of that. And then there's this note here. You're using the action in the context of a branch. You need to add this get branch track main or to name. Before running and expected since origin named one exist. And just make sure we're also using that. So this this action here will help you be able to check which you can run affected based on the the last successful deploy. If you're doing CI in our case, we probably don't don't need it. But it's because this is just for running test and let and build. But for a deploy action, we'd want to do based on the last successful deploy, not just the last made on main. Let's push this up. And there we go. And we've got this modified name, which is good. It's defected running in install. OK, let that fail. Something's weird here. I'm going to bail. And go to the next lab. So basically, this this whole lab is about setting up your. Setting up your CI to use to use NX affected and. And then running the. Running only the. Only only testing the code that you need to test. So. That that is one aspect of setting up CI. The other thing I wanted to show you to make sure we don't run out of time is in the next lab. I'm 12. Distributed caching. Distributed caching. So you can either run this or you can run. And X connects to an X cloud. And so it's basically saying yes to the thing you said no to before. And what that'll do is that adds a. Another package here. This X cloud package. And it adds some settings to your X JSON file. It changes a runner from the local. Task runner caching system to using the X cloud distributed caching system. So and then you also I think these are just the default cache of operations, so you can specify which things make sense to cache here. So build typically is cacheable. Basically, it's anything that is own will only change based on the output. Only changes based on the inputs. Things like serve don't make sense to be cached here because there's no output to actually actually cache things like anything that's like making a network So sometimes ETS should not be cached if they're making a network call to some other server that you're not like controlling on the end test. But it's up to you how you depend on how you wrote your intent test. That could be cashed or cannot be cached. The. There are some other things that don't make sense to be cached like a deploy. You don't want to cash or deploy step. So you define what should be cached here. And then this the add a package here. This is from last year. And then this the adding in X cloud also gave you this this access token. And so what we're going to do is. So when we ran this get send us this link here. To. The claim our workspace on X cloud. I'll just continue with Google and I'll use I use my normal. Yes. So this is the access token. So this is the one that's from the. Then it's a JSON file. It's this access token. Say, hey, I'm going to use this. So I connect it. Okay, so now what this does is when I run. I can run an X build CLI here. So it successfully ran to targets and then I can run a run. So it successfully ran to targets. Now that this this message was not here before. So if I click on this link here. Bring it back over. I can see the results of this run. So this means it was a. It was a cache miss here. This check means it was a successful run. They all took less than a second, even though it was a cache miss. But. That's that's information about it now. I could also. I could run this again. And this time read the output from the cache for three out of three tasks. And I'll look at looking here again. He's. Opening it up in a separate window here. Still success, but now this this icon here changes. It was a local cache. That's what this icon here means. So it's read it from my machine and loaded up. Now I can. I'm going to do this. Stupid caching. A long committed. Sure. Push that. Push that up. Actually, let me check out main. Well, here I'm going to merge this in and then check it out. Merge that in for a merge. Now I'm going to what I'm going to do is I'm going to check this out in a different. In a different folder. Copy that. And I'm going to show you the. The cache mechanism. It clone this and then BG Horde copy. I'm going to code BG Horde copy. I'm going to run MPM install to get everything set up. And what was the command I ran? I think it was build the CLI. Yeah, and it's built CLI. OK, so I ran that and now I'm going to run an X build CLI here. So this is a completely different repo here. I could run this on a different machine, but it'd be harder for me to set up. But if you want to test this yourself, you can download. Download my repo and run an X build CLI. And so successfully ran the target build. And one task depends on the zero run run from cache. Let's see. I wonder if that's because I have a read only token set up. OK, so this was a cache miss and it ran it all again. So let me see. OK, so I'm going to set up a. Generate a new token here. So the two different kinds of tokens BG Horde. Setting managed tokens. There's two different kinds of tokens. Now, this is a read write token. Why is that different? Hmm. OK, so I'm going to run a read write token. Why is that different? Hmm. I wonder if it's. Oh, there's a change here. What's different? My package like changed. OK, so that broke the. Next build CLI here. Oh, OK, I'm going to commit this. OK. Package like changes. Push this up. So because the package like changed, it should it correctly broke the cache is correctly said, I need to run this again, because if there's different a different package lock, then that means we've got a different different inputs coming in the system. So I'm going to run this index build CLI again. OK, so that's correctly read it from cache. Let me go back to the now I'm on the original repo here. And I'm going to. Get checkout main. Oh, I think that's that's the issue. I was on a different branch and there's some other things that were different. If you install. OK, so nothing. Nothing is different here. OK. So by default are the cache is stored inside of node modules dot cache. This folder here. And this has a hash for each. Each command I've run and then the outputs of them. So the terminal output and the. The disk folder outputs for each of these things. This is where things get read from. So I'm going to run NX reset, which will clear out that cache here. Show it to you. I didn't. And X. And. There's a command for clearing the cache, you find it. Or I can just delete the folder, but. I would maybe maybe it's just. OK, that's just my file system. The VS code thought it was still there, but it wasn't. OK, so NX reset did did get rid of it. So deleted that folder. So now if I run NX build CLI. It should go fetch the remote one that was created. And it should get rid of it. So I'm going to run that. And I'm going to run. The remote one that was created. Created here. And and loaded in. So NX build CLI. There we go. So this one was loaded from the remote cache. So read the output from the cache instead of running the command for two out of two tasks. It reused two tests, so did two of them were done remotely. So if I look at this, this command, this report here. Oh, yeah. This report, so this this icon here says it's a remote cache, so not instead of locally on my computer, it was remote pulling it down from from the next cloud. So and then you can, you know, get the terminal output if you want. No terminal output for that one. This one had terminal. So NX cloud is a it's a paid product, but it's for smaller repos. It's there's there's a large free tier. So if you have like 10 developers, you'll you'll probably never, never go past the free tier. I think we got 500 hours of compute time of time saved per month free. So typically, you know, a small organization, you'll never hit that. And it's free for whatever size. If you're if it's an open source repo. So for for larger organizations, there's there's a paid paid version. And there's also an enterprise version if you want to if you want to host the cache on your own servers that you can enable that way. So this sets up so that, you know, you're across your whole organization. If you if anybody ever runs a build on a particular set of code, you can share that work for anybody in the whole organization. All right. I've been talking for a long time and haven't looked at. Looked at the questions. So let me see if I've missed anything. All right. I am not seeing questions. So we've got like a few minutes left. What what other questions do we have? Is there any way to do distributed caching without NX cloud? So theoretically, you could roll your own there. I mean, NX cloud is a very powerful cloud. It's a very powerful cloud. So what is the best way to do distributed caching without NX cloud? So theoretically, you could roll your own there. I mean, NX itself itself is open source. NX cloud is not open source. But if you wanted to write your own distributed caching mechanism, you could. That part is not too difficult. The other piece that I forgot to show you is the distributed caching is great for your for your average use case. Let me share my screen again. So you could theoretically, I don't know, in a week or two of development time, write your own distributed caching, just like find the api and the source code and write it yourself. But I don't know why you would need to, frankly, because the pricing is not bad. But the other feature that I wanted to show you that NX cloud also offers is distributed task execution here. So distributed caching helps with the average case. And so, you know, if you've if you, you know, modify something that somebody is already already done, then you just use their their their results. But what if you modify something that's at the root of your of your repo or something that's nobody has has calculated already for you? Then you'll need something like distributed task execution. And what this does is it automatically helps parallelize all your tasks in the the optimum way possible because NX knows your dependency graph. So typically, your any attempt to parallelize things in the CI looks like this, where you've got if you've got you've got three different runners, you have some idle time and then you run tests and some idle time on your builds and then some idle time afterwards. And then you land and then a bunch of idle time. So, you know, manually setting up which which tasks run on which servers. But what DTE does is it you just tell it how many agents you want and and then it automatically splits up tasks among those agents in the most efficient way possible because it knows which tasks need to be run before which other tasks. So it says, I'm going to run those first on this agent. And as soon as that's done, I'm going to be adding in other tasks on top of that. So it makes the most most use the best use possible of however many agents you have. And if you ever need to scale up, you just change a three to a to a 10 and it's instantly scaled up to 10 agents. Whereas if you did it manually, that's a lot of work to to try to set that up. That's the that's a feature that it would take you, I don't know, I don't know, three or four months of development time to to get it to where it is now. And by that point, we'd have we'd have moved on to something else to add more features and stuff. So and that's that's a more killer feature than than distributed caching is even. Because that that helps you with your with your worst case scenario, something that would take two hours will take a half hour, whereas distributed caching, basically, it'll take your average of average of a half hour and make your make it be an average of 15 minutes or something. Right. Other questions. You can unmute if you want to talk verbally. Or if you're zoned out, that's fine, too. So NX itself is completely free and X cloud is the only paid portion. And, yeah. If you have let me actually point out some of the community stuff. So if you would like to chat with other people who are working on an X or, you know, trying to use an X. If you go to the index that this link on the right over here is the community slack. And you can follow people on Twitter. We also publish a lot of stuff on Twitter. We also publish lots of videos on YouTube or just, you know, interact on GitHub, too. So this this area over here has all of our community areas, community things. Yeah, well, it's been great. Presenting with you. Thank you for your attention and. Hi to everyone who comes back and watches this later. Good. Good work watching all the way to the end of the video. Thank you very much. Yeah, you're welcome. That was great. All right. Thanks, everyone. Bye bye.
160 min
06 Apr, 2023

Watch more workshops on topic

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career