DEV Community

Cover image for Building a Full-Stack App with Turborepo, React, and Hono (Part 1: Project Setup and Database Configuration)
PARTHIV SAIKIA
PARTHIV SAIKIA

Posted on

Building a Full-Stack App with Turborepo, React, and Hono (Part 1: Project Setup and Database Configuration)

What are monorepos?

According to google, a monorepo is a single version control repository (like Git) that contains the source code for multiple projects, applications or modules. Instead of having separate repositories for each project, a monorepo consolidates all code into one centralized location.

Advantages of monorepo:

  1. Dependency management is simple.
  2. Refactoring is easier.
  3. Collaborating in a monorepo is easier.
  4. Monorepos are more easy to test and deploy.

In this tutorial we will be building a simple To-do app with a monorepo structure. Since monorepos can be difficult to manage on their own we will use Turborepo.Turborepo by Vercel is a build system for javascript and typescript code bases.

Tech Stack for the project:

In this project we will be using the following technologies:

  1. React router v7 framework mode for frontend.
  2. Hono for backend.
  3. PostgreSQL as the database.
  4. Prisma ORM as the orm.

What will you learn from this tutorial:

After the end of this article you should have knowledge about:

  1. Data mutation (actions) and data loading (loaders) in react router.
  2. API development in hono.
  3. Database query with prisma.
  4. Managing everything with turborepo.

"You can follow along with the project's development in this repo. The main branch will be updated as the series progresses. For this article, the complete code can be found on the part1-setup-complete branch."

Prerequisite:

  1. Node v18+ should be installed.
  2. pnpm should be installed globally
npm i -g pnpm
Enter fullscreen mode Exit fullscreen mode
  1. Your favorite code editor.

Let's start coding

1. Creating the monorepo

Now to create a monorepo go to your desired location and run this command from your terminal.

pnpm dlx create-turbo@latest
Enter fullscreen mode Exit fullscreen mode

Give your monorepo a name you wish. I am keeping it simple and naming it todo-turbo.

Image showing how to give the name of the repo.

Now chose your package manager. I am using pnpm so if you want to follow along you can chose it too but it doesn't matter a lot.

Image showing how to chose package manager.

Now after you press enter, turborepo will install all the packages by itself. After the installation is complete a folder named todo-turbo will be created. The folder will have this structure.

Image showing folder structure.

2. Removing unnecessary folders

You can see that the project has two folders: apps and packages. The apps folder usually contains the source code and the packages folder contains the shareable code between these apps.

The apps folder currently contains two sub-folders: apps/web and apps/docs. Both of them are Nextjs repositories. Since we are going to use React-router v7 so we can safely remove these folders. Run this command from the todo-turbo/ directory to remove the apps/web and apps/docs folders from the apps folder.

rm -rf apps/web apps/docs
Enter fullscreen mode Exit fullscreen mode

3. Installing React router v7 for frontend

Now the apps/ directory is empty and we will install react router v7 here in a folder with the name frontend and honojs in a folder with the name backend.

To install React router v7, run this command inside the todo-turbo/apps directory

npx create-react-router@latest
Enter fullscreen mode Exit fullscreen mode

Enter the name of the project as frontend.

Image showing how to give the name of the project.

After that make sure to not initiate the git repository. Since this is a monorepo we have to keep the git repository at the root directory.

Image showing not to initiate git.

Now install the dependencies by choosing yes.

Image showing to install dependencies using npm.

And that's it. Your frontend is ready with React router v7.

How to remove the initialized git repository from frontend folder.

Don't worry if you accidentally initialized the git repository. You can remove it by following these steps. If you haven't initialized git you can skip to the next step.

  1. Go to the frontend folder in your terminal.
  2. Run this command
rm -rf .git
Enter fullscreen mode Exit fullscreen mode

4. Installing hono.js as the backend

For the backend we will use hono, Hono is a lightweight and fast framework that has support for many javascript runtime. We will be using hono with node. From the apps/ directory run this command to start a hono project.

pnpm create hono@latest
Enter fullscreen mode Exit fullscreen mode

Give the target directory a name of your choice. I will be naming it as backend.

Image showing name of the hono project.

Now for the template scroll down with the down arrow key and chose nodejs.

Image showing chosing nodejs as template

Now type 'y' to chose yes to install dependencies and chose pnpm as the package manager.
Now you are all set with the backend directory.

The apps directory has now two folders: frontend and backend. Explore these directories to get familiar with the code.

5. Setting up Prisma

We will setup prisma in the packages directory, this will allow us to share the types given by prisma across the whole monorepo.

Create a new folder named 'db' inside the packages folder. Now go into the todo-turbo/packages/db folder from the terminal and create a package.json file using the command.

pnpm init 
Enter fullscreen mode Exit fullscreen mode

Now we will have a package.json in this db folder. Change the value of the "name" key at the top of the file to "@repos/db". It is a convention to name internal packages in turborepo like this. You can name it however you want. Your package.json should look like:

{
  "name": "@repo/db",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

Now in this db folder we will install prisma. Run this command to install prisma and prisma client.

pnpm install prisma --save-dev
pnpm install @prisma/client
Enter fullscreen mode Exit fullscreen mode

Now run pnpm prisma init to initialize prisma. This command will create a prisma/ directory in the db/ directory, a .env file and a .gitignore file. Open the schema.prisma file inside the prisma/ directory in a code editor. You will see something like this:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
Enter fullscreen mode Exit fullscreen mode

Now we need to add models to define our tables in postgreSQL. For simplicity we will only have two models: User and Todo. Add these models to the schema.prisma file.

generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        BigInt   @id @default(autoincrement())
  name      String   @unique
  todos     Todo[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Todo {
  id        BigInt   @id @default(autoincrement())
  task      String
  dueDate   DateTime
  user      User     @relation(fields: [userId], references: [id])
  userId    BigInt
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
Enter fullscreen mode Exit fullscreen mode

Now we need a postgreSQL instance to store data. You can use this article to install and get started with in your device, otherwise you can ask any LLM to provide you with the necessary steps.

After you setup postgreSQL in your computer, create a database. I will create a database named "todoturbo". You can name it however you want.

Now open the .env file inside the db folder using a code editor. It should look something like this.

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
Enter fullscreen mode Exit fullscreen mode

Now replace johndoe in the DATABASE_URL with your username that you created during postgreSQL setup, replace randompassword with your password and replace mydb with your database name. After that save the file and generate the prisma client by running this command inside the db directory.

pnpm prisma generate
Enter fullscreen mode Exit fullscreen mode

Now the prisma client is generated along with all the prisma types. Now to export these types and the prisma client we will need to define a entry point for this db package. We will do it by creating a index.ts file and exporting from there.

So create a folder "src" inside the "db" folder. Create a index.ts in this src folder. This index.ts will be our entry point.

Add these lines of code to the index.ts . It defines the prisma client and exports it.

import { PrismaClient } from "../generated/prisma/index.js";
const prisma = new PrismaClient({});
export default prisma;
Enter fullscreen mode Exit fullscreen mode

Now to make this client available to other packages such as the backend package we will need to make some changes.

Add scripts and entrypoint to the package.json

{
  "name": "@repo/db",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "main": "./dist/index.js",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./client": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "scripts": {
    "build": "pnpm run clean && pnpm run db:generate && tsc",
    "dev": "tsc -w",
    "clean": "rm -rf dist generated",
    "lint": "eslint . --ext .ts,.tsx",
    "db:generate": "prisma generate",
    "db:migrate:dev": "prisma migrate dev --name init",
    "db:studio": "prisma studio",
    "db:push": "prisma db push"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "prisma": "^6.8.2",
    "typescript": "^5.3.3"
  },
  "dependencies": {
    "@prisma/client": "^6.8.2",
    "@repo/typescript-config": "workspace:*"
  }
}
Enter fullscreen mode Exit fullscreen mode

Add a tsconfig.json file in the packages/db folder

{
  "extends": "@repo/typescript-config/base.json", 
  "compilerOptions": {
    "outDir": "./dist", 
    "rootDir": "./src", 
    "declaration": true, 
    "declarationMap": true, 
    "sourceMap": true, 
    "esModuleInterop": true, 
    "module": "NodeNext"
  },
  "include": ["src/**/*.ts"], 
  "exclude": ["node_modules", "dist"]
}
Enter fullscreen mode Exit fullscreen mode

Let's go through this modified package.json . The scripts are pretty self explanatory. The "build" command builds all the dependencies and creates a dist/ folder. You can see that we have a new devDependency named "@repo/typescript-config". This is how we share libraries and code from internal packages in turborepo. The package "typescript-config" lies inside the packages folder and is named "@repo/typescript-config" in the package.json . The file packages/typescript-config/package.json should look like this.

{
  "name": "@repo/typescript-config",
  "version": "0.0.0",
  "private": true,
  "license": "MIT",
  "publishConfig": {
    "access": "public"
  },
  "files": [
    "base.json",
  ]
}
Enter fullscreen mode Exit fullscreen mode

Now run pnpm install from the root directory to install all the dependencies.

Modify turbo.json to build the repo

Now to build the repo we need to add output paths to turbo.json . Modify the turbo.json at the root directory to replace the default output paths.

{
  "$schema": "https://turborepo.com/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**", "build/**", "public/build/**"]
    },
  "lint": {
      "dependsOn": ["^lint"]
    },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's go through all the modifications. The first modification is that we have replaced the default output paths with output paths for our react router v7 frontend, hono backend and prisma.

"outputs": ["dist/**", "build/**", "public/build/**"]
Enter fullscreen mode Exit fullscreen mode

Now to import code from this db package to our backend package we need to install this internal package db in our backend package.

Installing db package in backend package.

We've already installed internal packages earlier and we will do it again in the same way. Open the package.json in the backend folder and add "@repo/db" in dependencies.

{
  "name": "backend",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@hono/node-server": "^1.14.2",
    "hono": "^4.7.10",
    "@repo/db": "workspace:*"
  },
  "devDependencies": {
    "@repo/eslint-config": "workspace:*",
    "@types/node": "^20.11.17",
    "tsx": "^4.7.1",
    "typescript": "^5.8.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

We have also added "@repo/eslint-config" in the devDependencies to get the benefits of eslint-config. If you want you can also install "eslint-config" in the frontend package but I will keep it as an exercise.

Now run pnpm install from the root directory to install all the packages. After that run pnpm run build to build the dependencies.

Wrapping up!

We have successfully completed the setup of the project. Now let's wrap up Part 1 with this. We have built the foundation of our Todo app with the frontend, backend and a db folder. In the next part we will build the APIs with Hono. So stay tune for that. Till then you can check out this blog which teaches how to build a professional sign up page using tanstack form.

Top comments (2)

Collapse
 
nabamallika_nath_3156c18d profile image
Nabamallika Nath

Wonderful 😀

Collapse
 
parthiv_saikia_ profile image
PARTHIV SAIKIA

Thank you 😊 very much