Configure TypeScript monorepo with Turborepo, ESLint, Prettier, and Webstorm.

Arthur Murauskas
7 min readJan 10, 2023

Nowadays, software development for the web is becoming increasingly broken down, given the restrictions of technology and expertise. They may not be produced in the same language — even if it is the same language the frameworks won’t match up; the code is built differently and deployed to different targets; plus, there can be separate teams dealing with frontend and backend. Even though we cannot do anything with regard to the technologies and fragmentation, we can mitigate the complexity for cross-functional teams and collaborative development — monorepos to save the day!

Poly-repo VS mono-repo

When it comes to choosing the right tools for a JavaScript or TypeScript monorepo, there is a difficult decision to be made as the options are vast. We’ve got Yarn Workspaces, NPM Workspaces, NX, Lerna, and now Turborepo. We have previously explored how to set up a TypeScript monorepo with WebStorm, ESLint, and Prettier using Yarn Workspaces. Today we’ll do exactly the same with Turborepo, which has become my preferred tool for a monorepo setup.

I intend to illustrate how setting up a monorepo with TypeScript has become simpler by following precisely the same structure as my previous article.

What is Turborepo?

It’s easier to say what it’s not: it’s definitely not a package manager, it works with npm, pnpm, and yarn, although they recommend pnpm if you can’t choose yourself.

Turborepo is a new kid in town, created more or less 1–2 years ago and acquired by Vercel, it is one of the best developer tools for JavaScript projects. It is written in Rust, it’s incredibly fast, and it removes lots of complexity of the monorepo management.

Besides managing multiple packages in a monorepo, it’s also a high-performance build tool and has a Turbopack, a high-performance successor to Webpack.

All in all, it sounds very promising; it is also backed by a company with a $3B valuation, so it’s not going to disappear any time soon. Let’s go and test it!

Installing Turborepo

To start with, let’s install Turborepo. As we will be using pnpm, let’s make sure that it’s set up too. I am going to use brew install pnpm; it can also be installed via npm install pnpm -g. If none of those options suit your needs, here are installation instructions for other platforms: https://pnpm.io/installation.

After that, launch npx create-turbo@latest, enter the name of the directory in which you’d like to init the repo, and turborepo will initiate the source code of the monorepo.

Initial configuration of Turborepo
Initial configuration of our monorepo

Directory structure

Let’s explore the folder structure of the entire project.

Turborepo directory structure
The directory structure of a Turbo monorepo

We have two main directories here in the project root:

  • apps — source files of our applications;
  • packages — re-usable code goes here and some config files, which we’ll see later.

Note: it is only a convention to use these two directories, on larger projects you can customize your monorepo setup to include more by editing the pnpm-workspace.yaml configuration file (we’ll see that later too).

If we’ll go into packages/ui, we’ll find a typical React package structure with package.json, tsconfig.json, and main index.tsx file and Button.tsx component.

Exploring packages/ui package

But the most exciting part is if we’ll go to one of the applications, for example, to apps/web/pages/index.tsx:

Exploring apps/web package

We see that we can import a local package ui without any problems! The import path looks great, as well, as it doesn’t include the absolute or relative paths to the packages. Out of the box, we’ve got something that had to be configured not that long ago.

TypeScript configuration

Let’s examine how to set up TypeScript. Different packages require varying tsconfig settings, and we should be able to make that happen.

There’s a packages/tsconfig package, which looks like a kinda obvious place to look at, but we’ll reverse-engineer a little bit and look at apps/web/tsconfig.json instead:

tsconfig.json of apps/web package

We can see that it extends tsconfig/nextjs.json and it’s interesting just how exactly we are referring to that file. Here’s how package.json looks like:

Exploring app/web package.json file
Exploring app/web package.json file

So, we are importing a package called “tsconfig” which we are then using to extend the typescript configuration.

Finally, let’s see what do we got in the packages/tsconfig:

File structure of packages/tsconfig
File structure of packages/tsconfig

We’ve got a base.json with a default configuration, which is then extended by nextjs.json and react-library.json configs. All of those are exported in packages.json.

This structure is very versatile, as you can group TypeScript configs in a package which can then be imported and extended by other packages or apps.

So far, zero configuration is required to make anything work in WebStorm. Everything works like a charm out of the box.

Prettier and ESLint configuration

Finally, we can configure ESLint and Prettier. If anything needs to be configured, of course. To this point, nothing else has needed to be set up. Let’s check the source files in packages/eslint-config-custom:

File structure of packages/eslint-config-custom
File structure of packages/eslint-config-custom

We’ve got a custom ESLint config that is exported from index.js. Let’s find out how it can be used in other apps and packages.

Looking at apps/web/.eslintrc.js:

Exploring .eslintrc.js of apps/web package
Exploring .eslintrc.js of apps/web package

tells us that we are just extending the custom ESLint config we defined in the package. I’m guessing that we also have to import that package in our package.json:

Exploring package.json of apps/web package
Exploring package.json of apps/web package

And yes, we are importing our local package from the workspace. There is, however, some under-the-hood magic going on, as we are importing eslint-config-custom but we are extending the custom config in .eslintrc.json. I suppose it’s an internal Turborepo notation that’s going to take the suffix out of eslint-config-*, so theoretically, we might have the possibility to maintain multiple ESLint configurations.

Again, ESLint works perfectly without any additional configuration in WebStorm.

Additional configuration

Let’s explore a little bit further and check the contents of turbo.json and pnpm-workspace.yaml:

Configuration of the root workspace
Workspace configuration

So, on the left, in pnpm-workspace.yaml, we are defining the structure of our monorepo. It shows that we can easily extend the default convention and have more than two root-level directories if needed.

On the right, in turbo.json, we are defining our tasks, which, according to documentation, once registered in turbo.json can then be run with turbo run <task name>, for example turbo run build.

That’s exactly what we’ll see if we’ll open the root-level package.json:

Root-level package.json
Root-level package.json

Final steps

We haven’t needed to make any extra changes in the IDE — Turborepo’s project structure gives us what we need, and that means we can start working right away.

Turborepo offers much more than just monorepo management; it also includes a build system, CI/CD integration, task caching, and other features which we aren’t discussing here. What we have seen in this article is barely scratching the surface, really, but it’s incredible how fast and easy the configuration is. In fact, we don’t even need any configuration and can start working on our project immediately, as Turborepo provides some really sane defaults.

In 3 clicks or keystrokes, we get all professional tools for JS/TS projects: TypeScript is supported by default, Prettier is there to guarantee consistent code style across the packages, and ESLint is pre-configured. The default folder structure is good, but it can be tweaked for larger projects. We can publish npm packages if needed or keep them private. There is a possibility to integrate with other tools to manage package versions or to use GitHub changesets.

My conclusion is that I see zero to no reasons to use Lerna or Yarn workspaces for most of my current projects from now on, and I’m really excited to see further improvement in the quality of professional tools in JavaScript/TypeScript space.

--

--

Arthur Murauskas

CTO and co-founder @ code.store. TypeScript enthusiast. Enjoy writing about Product Management and Software Engineering.