Skip to content

A full-stack mono-repo example providing automated code generation, e2e type-safety and optimal DX

Notifications You must be signed in to change notification settings

eli-lim/web3-monorepo

Repository files navigation

Web3 Full-stack Mono-repo Example

image

This is a full-stack mono-repo example for web3 projects.

It includes a NextJS front-end, a Node.js back-end, and a foundry subproject to maintain smart contracts.

The smart contracts produce Typescript bindings for use in both the frontend and backend, allowing for type safety across the entire stack.

The frontend and backend are set up to communicate with each other using tRPC.

Setting up

  1. Setup postgres on local dev for prisma
  2. Run:
    # Use the project's node version
    nvm use
    
    # Install dependencies
    pnpm install

Development

To start the whole stack for development, run the following command:

pnpm dev

Note

Notice that you don't have to run any build steps. The repo doesn't use any postinstall scripts either. Everything is handled robustly by Turbo.


How it works

Turbo-charged workspaces

Turborepo is used to manage task dependencies in a highly performant way. It does so by parallelizing tasks and caching where appropriate. The onus is on the developer to define the dependencies between tasks and whether their respective outputs can be cached.

This drastically improves DX by automating steps in the development process that would otherwise be manual.

  1. Code generation + compilation:
    graph LR
        c[contracts .sol] --generate--> a[artifacts .json]
        a --generate--> ts[typescript\nbindings]
        ts --imported--> backend
        ts --imported--> frontend
        backend --generate--> sdk
        sdk --imported--> frontend
    
    Loading
  2. Full-stack development:
    graph LR
      s((dev)) --> c
      c[code generation + compilation] --> backend[start:backend]
      c --> frontend[start:frontend]
      c --> chain[start:testing-chain]
      chain --> deploy[deploy:contracts]
      backend --> e((ready))
      frontend --> e
      deploy --> e
    
    Loading

Traditionally, one would have to either:

  1. Manually run each step during build / development - painful and time-consuming
  2. Write scripts (imperatively) to automate the process - error-prone, no built-in caching

Turbo allows us to declaratively define the dependencies between tasks and let it handle the rest.

turbo watch

This entire process can be run in watch mode, which means any changes to any source code will trigger the necessary downstream tasks to be re-run.