![]() |
A modern turborepo template for building fullstack projects with modular components, shared configs, containerised deployments and 100% type-safety.
Below is an overview of all the components in the stack:
apps
├─ web
| ├─ react (vite)
| ├─ tanstack (router, query, form)
| └─ tailwindcss
├─ server
| └─ hono (wrapper for api & auth)
packages
├─ api
| └─ trpc with valibot
├─ auth
| └─ better auth
├─ db
| └─ drizzle orm (postgresql database)
├─ ui
| ├─ tailwindcss
| └─ shadcn & radix ui
tools
├─ eslint
├─ prettier
├─ tailwind
└─ typescript
View all catalog dependencies in pnpm-workspace.yaml.
The following features are implemented out-of-the-box:
- login/register (using better-auth email/password) credentials provider
- themes (dark/light mode using next-themes)
- web/server integration (trpc API example for creating/listing posts)
Many aspects of the RT Stack were derived from the t3-oss/create-t3-turbo. However, there is a preference for:
- tanstack router (web) + hono (server) instead of nextjs (fullstack)
- better auth for authentication instead auth.js (next auth)
- valibot for input validation instead of zod
- tanstack form instead of react-hook-form
- using
.env
in each application/package instead of globally, as per turborepo's recommendations
Additionally, the aim of this project is to always adopting the latest releases of dependencies and tools. For example:
- react v19
- tailwindcss v4 & shadcn-ui (canary)
- trpc v11
- eslint v9
- pnpm v10
Ensure the following tools are available on your system:
- node (version 22+)
- pnpm (version 10+)
- postgres database, which you can easily run using tools like:
- docker and docker-compose
- podman and podman-compose
- supabase's free tier cloud database
# Create a repository using the rt-stack template (replace YOUR_PROJECT)
pnpm dlx create-turbo@latest -m pnpm -e https://github.com/nktnet1/rt-stack YOUR_PROJECT
# Enter the directory or open in your IDE (replace YOUR_PROJECT)
cd YOUR_PROJECT
# Install all dependencies for apps and packages
pnpm install
# Copy .env.example to .env for all applications and the @repo/db package
pnpm env:copy-example
# Start a local postgres instance in the background (e.g. using docker)
docker compose up db --detach
# Push drizzle schema to your database
pnpm db:push
You can then start all applications with
pnpm dev
By default the following URLs will be accesibile:
- web application: http://localhost:8085
- backend server: http://localhost:3035
When using an external postgres database (e.g. from supabase), you can skip the step that spins up a local postgres instance with docker.
Instead, you will need to modify the following environment variables:
-
SERVER_POSTGRES_URL
in the fileapps/server/.env
- used at runtime by the backend server in
pnpm dev
- used at runtime by the backend server in
-
DB_POSTGRES_URL
in the filepackages/db/.env
- used in database schema migrations with
pnpm db:push
- used in database schema migrations with
Use pnpm --filter=<name>
(where <name>
is
defined in the package.json
of each package).
Example usage:
# Install the nuqs package for our web application:
pnpm --filter=web install nuqs
# Format only the ui package:
pnpm --filter=@repo/ui format
You can get a list of all package names using the command below:
find . -maxdepth 3 -name "package.json" -exec grep '"name":' {} \;
To install a single Shadcn/UI component, e.g. button
, use the command
pnpm ui-add button
You can also open an intera
pnpm ui-add
- press
i
to enter interactive mode on startup - use
j/k
(or arrow keys) to navigate up and down. - use
<Space>
to toggle select your desired component(s) - hit
<Enter>
to install all selected components
All scripts are defined in package.json and turbo.json:
pnpm typecheck # repot typescript isses
pnpm format # report prettier issues
pnpm format:fix # auto-fix prettier issues
pnpm lint # report eslint issues
pnpm lint:fix # auto-fix eslint issues
pnpm clean # remove all .cache, .turbo, dist, node_modules
pnpx codemod pnpm/catalog # migrate dependencies to pnpm-workspace.yaml
Both the web
and server
applications have been containerised. You can start
see this in action by running the commands:
# Start all applications
docker-compose up
# Push drizzle schema to your database - while you can use `pnpm db:push` on
# the host machine if you have installed all the required dependencies, it is
# also possible to do everything within docker.
# Open a second terminal and run the command:
docker compose run --rm drizzle
# Upon completion, you will be inside the `drizzle` docker container instead
# of the host machine. It is now possible to push the schema with:
pnpm db:push
You can then open the web link below in your browser:
Please note that these containers are run in production mode. For further details, see
You can deploy applications to any services that supports docker deployment.
Using docker compose (see compose.yaml) is also an option, although this alone may not be production-ready at scale. However, it can be paired with
- reverse proxies and load balancers offered by tools like Traefik or Caddy
- container orchestration platforms like Docker Swarm and Kubernetes
Personally, I recommend deploying on a Virtual Private Server that has one of these Self-hostable PaaS installed, which automatically handles the complexity of deployment mentioned above for you:
- Coolify
- Dokploy
Do note that for the web application, the PUBLIC_SERVER_URL
variable
available at build time (as a docker build argument), rather than an environment
variable at runtime.
Also, both the server application's PUBLIC_WEB_URL
and the web
application's PUBLIC_SERVER_URL
needs to be set as internet-accessible URLs
when deployed, e.g. https://mycompany.com
and https://api.mycompany.com
,
rather than referencing http://localhost:8085
like in development.
The web application is a simple React static site powered by Vite, which is easily deployed to platforms such as GitHub/GitLab pages, Vercel and Netlify. You can refer to the vite documentation for deployment guides on all major platforms.
The server application uses the hono web framework with the NodeJS runtime. However, this can be exchanged with other runtimes before deploying to your chosen platforms. For example, deploying to Netlify is covered within Hono's documentations.
The following is configured in vite.config.ts web application:
TanStackRouterVite({
routeToken: 'layout',
}),
This is to allow for a layout.tsx
file in each directory similar to NextJS.
You can read more about this
here.
There is an artificial delay added in development mode to simulate API usage in
real-world environments. You can disable this by removing the timingMiddleware
in ./packages/api/src/server/trpc.ts
This template was made to follow the the recommendation of
- @tyleralbee in this turborepo's GitHub discussion
- @cjkihl in create-t3-turbo issue #397
- turborepo official docs on environment variables best practices
In using this template, it is recommended that
- each application has a local
.env
file instead of a global.env
at the root of your repository - packages should be pure, i.e. rely on factory methods and receiving inputs to
instantiate rather than consuming environment variables directly
- one exception is the
@repo/db
package, which requires theDB_POSTGRES_URL
variable for schema migration withpnpm db:push
- one exception is the
- environent variables are prefixed, e.g.
SERVER_AUTH_SECRET
instead ofAUTH_SECRET
. Caching in the app'sturbo.json
can then be configured to use wildcards such as:"tasks": { "build": { "env": ["SERVER_*"], } }
There is also a script that creates a .env
from .env.example
of each
app/package, which can be run with:
# NOTE: This will not overwrite existing local .env files
pnpm env:copy-example
# To reset any modifications to your .env and restore the examples, run:
pnpm env:remove
pnpm env:copy-example
It is recommended that any new apps that uses environment variables follow the example script set in apps/server/package.json.