Case for a monorepo

Chris Nager
4 min readMar 20, 2018

--

Code + design + file structure

🎈 Air

At Air, we’re building apps for capturing, storing, and managing videos. We currently offer an iOS app and a web app that share a good amount of code. This article walks through the steps we took to build a universal codebase and synced design system in a single Git repository.

Goal

My goal was to build a cross-platform codebase that synced with our design system and allowed for rapid code changes to increase our team’s output.

Universal code

I looked at our projects and found plenty of reusable code and shared folders. Our apps share utilities, constants, config settings, i18n strings, linting rules, and React Native components (thanks to react-native-web). With this information, I was able to split our code into reusable packages.

Initial plans to create Air’s universal system

Synced design system

We use Storybook to generate our design documentation. Our docs use Air’s shared components which keeps our design system and code in perfect sync. When a change is made to our components, our apps and docs are updated.

Air’s design system docs

Finding our optimal file structure

First implementation: Practice repos

Our initial code organization strategy was to keep our app repositories separate. We then built functional practice projects that contained essentially reusable code for us to copy-paste into our apps. As we moved quickly, this was an easy way to stay in sync for a small team, but was ultimately not a sustainable solution.

  • ✔ Simple
  • ✘ Hard to stay in sync
  • ✘ Duplicated code
  • ✘ Not sustainable
Oh, bother.

Second implementation: Polyrepo structure

I then built a polyrepo structure, abstracting all shared packages into their own repositories (constants, utilities, config, components, etc.).

Our apps consumed these packages as defined in their package.json file. This was clean, but led to dependency management chaos. Every time a change needed to be made to a dependency, we were forced to jump into a separate repo, update it, bump the package version (if need be), hop back into our original project, and reinstall the dependency. Another issue we ran into was, because we serve our web app and site on Netlify, we had to create workarounds for reusing our Git credentials to install our private repositories.

  • ✔ Clean
  • ✔ Reusable code
  • ✘ Slowed down development time
  • ✘ Dependency management is difficult
  • ✘ Issues with Netlify builds
Think, think, think!

Third implementation: Monorepo

Finally, I used Yarn Workspaces to build a single repo that hosts all our projects and handles our dependencies in a more maintainable way.

  • ✔ Clean; all dependencies are in a single packages folder
  • ✔ Reusable code
  • ✔ Consistent linting and project configuration files across all packages (.eslintrc, .babelrc, .gitignore, prettierrc.json, etc.)
  • ✔ Cross-platform testing is easy to set up
  • ✔ Simple releases with Lerna
  • ✔ Allows for rapid development across dependencies
  • ✔ Simple dependency management
  • ✔ Contains our synced design system
  • ✔ Works perfectly with Netlify builds
“Nobody can be uncheered with a balloon.” ― A.A. Milne, Winnie-the-Pooh

🏆 Goal achieved

Thanks to our new code organization, we now have:

  • A universal codebase with DRY, shared code between projects
  • Synced design docs that use our shared components
  • Rapid, cross-project code changes and releases in our new monorepo

Though a monorepo worked for our purposes (and for others [Facebook, Google, Twitter, Babel, React Native Web, React Router, Meteor]), be sure to review the pros and cons before implementing one.

Takeaways

  • Consistency is underrated. Tools like Prettier and ESLint are spectacular for working with a team across multiple projects.
  • Using lerna import or git subtree carries over a repo’s Git history and combines its commits chronologically with the monorepo’s commit history. We’re still finding the best way to compose our commit messages for the monorepo as commits can span multiple packages. We may try namespacing our commits. (e.g. [web, components] Refactor gallery)
  • An exciting aspect of building a business from the ground up is the speed at which we’re able to adopt or reject new technologies. I’m proud of our team for their ability to move quickly and willingness to try new things.

$ >whoami

I’m Chris, a designer-developer that has worked in advertising, in big tech, on Wall Street, and am now building a startup in a Brooklyn warehouse. I care deeply about accessibility and performance in my apps.

Mash the clap (👏) button if you found this article helpful or want to show your support for our startup. Thanks!

--

--

Chris Nager

Senior software developer @airhq / formerly @iex and @salesforce