Introduction: Full-Stack Monorepo Goals
In this video, we are going to create a monorepo using Turbo Repo with NestJS as a backend, Next.js as a web/frontend. And we are going to create shared packages where we can put our UI components and our shared types that are going to be shared between these two projects. With this setup, you can build basically whatever you want.
And as always, you have all this code available in the description below. There is a link to a public GitHub repository you can check it out and try it on your machine. And now let's see how to build this and what are we getting from this starter kit.
Initializing the Turbo Repo Monorepo
First thing we need to do is to initialize our Turbo Repo. So we are on turbo.build/repo/docs. I'm going to put this link into the description below. Here you can read what is actually Turbo Repo. So it's a high-performance build system for JavaScript and TypeScript codebases and it is designed for scaling monorepos.
And we can see below which problems is it solving exactly. So it solves your monorepo scaling problem and remote cache stores the results of all your tasks, meaning that your CI never needs to do the same work twice. So basically, it's scheduling all of our tasks for maximum speed. So let's now install it.
So I'm going here to the installation page and we can get started with Turbo Repo with just running this npx create-turbo@latest. So I'm going to copy this command and paste it inside of my terminal. So I'm inside of my projects directory and I'm pasting npx create-turbo@latest and I'm going to call my project turbo-repo-full-stack-starter-kit like this. And I'm going to use pnpm.
So now everything is being downloaded and as you can see, Turbo Repo is installing our application packages. One is the documentation, second one is the web. And we have three library packages, which are UI, TypeScript config, and ESLint config. So these are shared among our projects, and in our case, it's docs and web.
Exploring the Default Next.js App
So now everything is ready, we can see that it is successfully installed and we can go to our turbo-repo-fullstack-starter-kit and run it with pnpm dev like this. So now we see that we have Next.js 14.2.4 running on our localhost:3000. And if we open it, we can see that it is successfully installed and we have our front-end Next.js application running.
If we open our code now, I'm going to my projects again and Turbo Repo, and I'm going to open this one with code .. So here is my cursor, I opened the turbo-repo-fullstack-starter-kit, here it is. And we can see here that inside the apps directory we have our web and inside, we have a Next.js application. And inside the docs, we have another Next.js, that one is for documentation only, we're not going to touch that.
And inside of our packages, we have the shared UI directory and inside we have just Button, Card, and Code. So here we would probably put Shadcn or some other UI library but we are going to leave that one for now. What we need is our back end.
Scaffolding the NestJS Backend
And we are going to use NestJS. So now here inside of our apps directory, I'm going to create a new directory and I'm going to call it backend like this. And we are going to that same directory here inside of our terminal, so it's apps/backend. And here we are going to initialize our NestJS application. So I'm searching for NestJS on Google and I'm opening the documentation. There we are going to the documentation here and we are going to our first steps. So how are we going to create it? It's just nest new. So I'm running nest new . to say that I'm in this current directory.
So for this one, you need to install the Nest CLI. This one I have it already installed, but you need to do it globally. And we are going to create here our new NestJS application. I'm going to use also pnpm. And now we can see that installation is in progress and it should be pretty much... Yeah, here it is. It's really quick. And now we have our Nest working inside of our backend directory.
Configuring and Running the NestJS App
So we can quickly test it out with nest start and we are going to see if it's going to work. We have already port 3000 working, so we need to change that one here inside of our NestJS application. We are going to the sources, to main.ts, and here we are going to change from port 3000 to port 5005. I'm usually using this port because that one is always used for Express applications.
And now we can try again. So here I'm going to type nest start and we see that our application is now running successfully on port 5005. So now we can basically already call this NestJS application from our, let's say, page here inside of our Next.js application, but we still have a couple more things to do.
Creating a Shared Types Package
For example, first thing is that we need our types shared between our NestJS and Next.js application. So how are we going to do that? I'm going to collapse all these folders and here inside of our packages, I'm going to create a new directory and call it types. So inside I'm going to create a new file, package.json, and inside of that file we are going to just name it... let's name it types like this. And those are shared types for the repo. And we are going to use our index.ts.
So here we need our index.ts file, and I'm going to create a new directory and call it just src like this. And let's imagine that we are going to use some users and here I'm going to export the interface User like this. So we have id, name, and email. That's usually what we have inside the users. And here inside of our index.ts, we are going to export everything from our src/users like this. So now we are basically exporting this index as a shared package.
Consuming Shared Types in Next.js
And that one needs to be a dependency inside of our app. So here, for our Next.js for example, we are going to our package.json. And same as here, as you can see here we are using @repo/ui. So we have here our... if we check here packages and then ui. So here it's the same thing for our UI. So here we are exporting it as... so here we are using Button, Card, and Code and we are going to do the same thing for our types.
So here inside the package.json in the web, we are going to add a new dependency and that one is going to be @repo/types. And we can now use it inside of our page or wherever we want. We can use all these types from our package. So if we want now, let's just test it out quickly here inside of our page. Let's create a new user. So that one is going to be user and it's going to be type of User and we can just create something: id, name, and email. And we need now to import it here from @repo/types. And I think I need to first here... just, yes, we need to name it here @repo/types. So we need to be consistent with this one.
And now here let's see what is it complaining about. So it cannot find a module. Let's just restart our... let's do again the whole pnpm dev and then it should see it probably. And do install just in case. Here it is. And now pnpm dev. So now it should see it. Yes, here it is. And now we have here our User type, which is exported from our package which is called types. And now we can share this same type between NestJS and Next.js. How awesome is that?
Building the NestJS User API
Now we can create quickly some API inside of our NestJS application and call it from our Next.js app. So let's go again to the documentation. Here, I'm going to the controllers and we are going to use this command, so that's nest g resource. And that one is basically going to create the REST API for us, whole CRUD entrypoint. And I'm going to call it user. So now we are creating... I'm going to choose here REST API, and yes, I'm going to create CRUD entry points. So now it's ready and we can check here. So inside the backend directory we have here user and inside we have the controller, module, service and everything we need.
So basically, now we can call this findAll. So let's try it out. I'm going to run again nest start. And here it is. So we can see here already that we have a user API endpoint. And here we can call the localhost:5005 and run it on Google like this. Let's just go localhost like this, and we can already see here /user and this action returns all user. So that is awesome. And we are just going to put here to mock some kind of database like this and we are going to return, let's say, three users. Cursor is doing its thing. And now if you refresh here... again, refreshing. Did I... Oh, I need to run here nest start. I didn't run the one that is checking all for all the changes. And here now, we can see that this one is returning three users that we are returning from our service. So this one should be returned from some database. We are not going to do that now. Let's just do this simple example and show it inside of our nextjs application.
Connecting Next.js to the NestJS API
So what are we going to do now? We are going to our Next.js, and here I'm going to create inside the web directory, I'm going to create a new server directory and there I'm just going to create users.ts. So this is that, let's say, bridge between our apps, and we are going to create just a simple getUsers, and then we are going to extract it from our localhost:5005/users. Except it's not good as always to have this hardcoded, so we are going to create a new env.local and inside of that file, we are just going to create API_URL and there we are going to call for the http://localhost:5005.
So now here inside of our users.ts, we are just going to call for that process.env.API_URL/users. And now we can call this getUsers from our page. So here, instead of this const user, we are just going to say that users equals to getUsers, and that one is being imported from server/users. And we are going to turn this one into an async function and here we are going to await for all those users. And those users are going to be the type of User. So this one is now coming from our package, from our shared package. And let's just delete all this default stuff from Next.js, and here we are just going to map through our users. Maybe it's actually better to create a list like this. And instead of this div, we are going to put the li. Okay, so let's test it out.
Debugging and Final Result
So now here we have users.map is not a function. Okay, let's see what's going on. We are going into our console and let's see, do we have somewhere the error? So this one is actually not looking probably good from... Yes, we have 500. Okay, so we have something... oh, this one should be not users but user. And now here, here it is, awesome! So we are now getting our John Doe, Jane Doe, and John Smith from our NestJS.
And let's see where is it again. So here inside of our user service, we are sending these three users from findAll method. So this one should be some query to our database to return all users. And then it's going, of course, through a controller. Here we are creating our endpoint. Then we are going to our bridge, which is users in our web app, which is a Next.js application. And here we are just fetching those users from our API endpoint. In this case, it's /user. I should have named it users and not user. And now we just take the data on our page. And here we are using getUsers from our server/users and we are getting our type for our users from the shared package. And we are calling that one as an asynchronous function inside of our server component. So everything is fetched from the server and we are just displaying it on our main index page.
Conclusion & Next Steps
I hope I helped you out with this one, warriors. So now we have basically a starter kit ready to create any full-stack application we want. And we don't need to rely on Vercel and its serverless functions and all the money problems. We can use our own backend.
And this doesn't need to be NestJS, it can be Express, it can be whatever you want. And if you want some project going in this direction, just tell me in the comments and I'll try to create something. Until next time, see you.