A production-ready Node.js boilerplate built with tRPC, MongoDB, and Redis — featuring end-to-end type safety, JWT authentication, role-based access control, real-time WebSocket subscriptions, and Redis caching out of the box.
- Overview
- Features
- Tech Stack
- Project Structure
- Prerequisites
- Getting Started
- Environment Variables
- Available Scripts
- Docker
- API Reference
- Architecture
- Security
- Contributing
- License
tRPC Starter is a batteries-included backend starter template for building type-safe APIs with Node.js. It eliminates the boilerplate of setting up authentication, caching, logging, error handling, and real-time communication from scratch, so you can focus on building your product.
- End-to-end type safety — tRPC with SuperJSON serialization; share types directly between server and client
- HTTP + WebSocket transport — single tRPC router served over both HTTP and WebSockets on the same port
- Real-time subscriptions — event-emitter–based pub/sub with observable subscriptions
- JWT authentication — Bearer token and cookie-based auth via reusable tRPC middleware
- Role-based access control —
publicProcedure,privateProcedure, andadminProcedureout of the box - API key guard — header-level
x-api-keyvalidation middleware - Redis caching — singleton
RedisCacheclient with configurable TTL per procedure - MongoDB integration — Mongoose ODM with pagination support (
mongoose-paginate-v2) - Input validation — Zod schemas with descriptive error messages
- Structured logging — Winston with JSON formatting and file transport for errors
- Security hardening — Helmet, CORS, bcrypt password hashing
- Docker-ready —
Dockerfileanddocker-compose.ymlfor MongoDB and Redis
| Layer | Technology |
|---|---|
| Runtime | Node.js 20+ |
| Language | TypeScript 5 (strict mode) |
| API Framework | tRPC 10 |
| HTTP Server | @trpc/server standalone adapter |
| WebSockets | ws + @trpc/server WS adapter |
| Database | MongoDB via Mongoose 8 |
| Cache | Redis via ioredis 5 |
| Authentication | JSON Web Tokens (jsonwebtoken) |
| Validation | Zod |
| Serialization | SuperJSON + devalue |
| Logging | Winston |
| Security | Helmet, CORS, bcrypt |
| Package Manager | pnpm |
.
├── src/
│ ├── app.ts # Server factory — HTTP + WebSocket setup
│ ├── server.ts # Application entry point
│ ├── types.ts # Shared TypeScript types and interfaces
│ │
│ ├── config/
│ │ └── index.ts # Centralised environment configuration
│ │
│ ├── trpc/
│ │ ├── index.ts # tRPC initialisation; procedure builders exported here
│ │ ├── context.ts # Request context factory
│ │ └── routes.ts # Root router — compose all sub-routers here
│ │
│ ├── modules/
│ │ └── auth/
│ │ ├── auth.router.ts # Auth tRPC router (register, login, subscription)
│ │ └── auth.service.ts # Auth business logic
│ │
│ ├── db/
│ │ ├── db.ts # MongoDB connection
│ │ ├── operations.ts # Generic DB query helpers with pagination
│ │ └── models/
│ │ └── User.ts # Mongoose User model
│ │
│ ├── middlewares/
│ │ ├── auth.ts # JWT authentication middleware
│ │ ├── apiKey.ts # API key validation middleware
│ │ ├── cache.ts # Redis caching middleware (configurable TTL)
│ │ ├── checkRole.ts # Admin role-guard middleware
│ │ ├── errorHandler.ts # Global tRPC error handler middleware
│ │ └── combinedMiddleware.ts # Express-level CORS + Helmet chain
│ │
│ ├── cache/
│ │ └── redis.ts # Singleton RedisCache client
│ │
│ ├── errors/
│ │ └── appError.ts # Custom AppError class
│ │
│ ├── utils/
│ │ ├── getUserFromToken.ts # JWT verification helper
│ │ ├── logger.ts # Winston logger instance
│ │ ├── parseCookie.ts # Cookie string parser
│ │ ├── response.ts # Standardised success response helper
│ │ └── validApiKey.ts # API key validation helper
│ │
│ └── validations/
│ └── auth.ts # Zod schemas for auth input
│
├── Dockerfile
├── docker-compose.yml # MongoDB + Redis services
├── tsconfig.json
└── package.json
- Node.js >= 20.x
- pnpm >= 9.x — install with
npm i -g pnpm - MongoDB >= 6.x (or Docker)
- Redis >= 7.x (or Docker)
git clone https://github.com/PrantaDas/tRPC-starter.git
cd tRPC-starterpnpm installCreate a .env file in the project root. See the Environment Variables section for a description of each key:
cp .env.example .envThe included docker-compose.yml spins up MongoDB and Redis with persistent volumes:
docker compose up -dpnpm devThe server starts on the port defined in your .env (default 7000). Both HTTP and WebSocket connections are accepted on the same port.
Create a .env file in the project root. All variables are required.
| Variable | Description | Example |
|---|---|---|
NODE_ENV |
Runtime environment | development |
PORT |
Port the server listens on | 7000 |
MONGO_URL |
MongoDB connection string | mongodb://localhost:27017/trpc-starter |
JWT_SECRET |
Secret key used to sign and verify JWTs | a-long-random-string |
COOKE_KEY |
Secret key for signed cookies | another-secret |
X_API_KEY |
Static API key for x-api-key header validation |
my-secret-api-key |
REDIS_HOST |
Redis host | localhost |
REDIS_PORT |
Redis port | 6379 |
CORS_ORIGINS |
Comma-separated list of allowed CORS origins | http://localhost:3000,https://myapp.com |
Security note: Never commit
.envto source control. Add it to.gitignore.
| Script | Description |
|---|---|
pnpm dev |
Start the development server with hot-reload via nodemon |
pnpm build |
Compile TypeScript to dist/ |
pnpm start |
Run the compiled production build from dist/server.js |
docker compose up -dStarts MongoDB on port 27017 and Redis on port 6379 with named volumes for data persistence.
# Build the image
docker build -t trpc-starter .
# Run with environment variables
docker run -p 7000:7000 --env-file .env trpc-starterThe Dockerfile uses Node.js 20, installs pnpm globally, compiles TypeScript, and launches the production server.
tRPC does not expose REST endpoints. The client calls procedures by name using a type-safe client generated from the Routes type exported from src/trpc/routes.ts.
Three procedure builders are provided in src/trpc/index.ts. Use them when adding new routers.
| Procedure | Auth Required | Role Required | Use Case |
|---|---|---|---|
publicProcedure |
No | — | Unauthenticated endpoints (e.g. login, register) |
privateProcedure |
Yes (JWT) | user or admin |
Any authenticated user |
adminProcedure |
Yes (JWT) | admin only |
Admin-only operations |
All three automatically include the global errorHandler middleware.
Authentication is resolved via:
Authorization: Bearer <token>header, or- A signed cookie named
some-cookie
Base path: auth
Register a new user account.
Input
{
name: string; // min 2 characters
email: string; // valid email format
password: string; // min 6 characters
}Response
{
success: true;
message: "User Created";
data: {
_id: string;
name: string;
email: string;
role: "user" | "admin";
createdAt: string;
updatedAt: string;
}
}Fires a user-registered event on successful registration, consumed by the onUserRegister subscription.
Authenticate with email and password. Returns a signed JWT.
Input
{
email: string;
password: string; // min 6 characters
}Response
{
success: true;
message: "User Logged In";
data: {
token: string;
user: {
(_id, name, email, role, createdAt, updatedAt);
}
}
}Real-time event stream that emits an IUser object whenever a new user registers. Requires a WebSocket connection.
- Create a directory under
src/modules/<module-name>/ - Define your router in
<module-name>.router.tsusing the appropriate procedure builder - Register it in
src/trpc/routes.ts:
import { myRouter } from "../modules/my-module/my-module.router";
export const routes = router({
auth: authRouter,
myModule: myRouter, // add here
});Wrap any query procedure with the cache middleware to add Redis-backed caching:
import { cache } from "../../middlewares/cache";
export const myRouter = router({
getItems: privateProcedure
.use(cache(120)) // TTL in seconds
.query(async () => {
// result is automatically cached for 120 seconds
}),
});Cache keys are automatically scoped to the procedure path and serialized input, so different callers with different inputs receive independent cache entries.
Client (HTTP or WebSocket)
│
▼
combinedMiddleware
(CORS + Helmet)
│
▼
tRPC Router
│
┌────┼──────────────────────────────────────┐
│ │ │
publicProcedure privateProcedure adminProcedure
(errorHandler) (auth → errorHandler) (auth → checkRole → errorHandler)
│
│ [optional] cache middleware (Redis TTL)
│
└──► Module Router (e.g. authRouter)
│
Service Layer (e.g. AuthService)
│
┌─────┴──────┐
│ │
MongoDB Redis
(Mongoose) (ioredis)
Key design decisions:
- Singleton Redis client —
RedisCache.getInstance()ensures a single connection is reused across the entire application lifecycle. - Procedure builders as access-control primitives — security tiers are enforced at the procedure definition level, not inside handler logic.
- Event-emitter subscriptions — WebSocket subscriptions use Node's built-in
EventEmitterfor a zero-dependency pub/sub mechanism. - Centralised config — all
process.envreads are funnelled throughsrc/config/index.ts; no other module reads environment variables directly. - Layered middleware — Express-level middleware (CORS, Helmet) handles transport concerns; tRPC middleware handles application concerns (auth, caching, error handling).
This template applies the following security practices by default:
- Password hashing — bcrypt with a cost factor of 10 before any persistence
- HTTP security headers — Helmet sets
Content-Security-Policy,X-Frame-Options, and other protective headers on every response - CORS allowlist — only origins listed in
CORS_ORIGINSare permitted - Secrets via environment variables — JWT secrets and API keys are never hardcoded
- Sensitive field stripping —
passwordand__vare removed from all User model JSON responses via atoJSONoverride - Input validation — Zod schemas reject malformed data before it reaches service or database layers
- Structured error responses — internal errors are caught and re-thrown as
TRPCErrorto prevent leaking stack traces to clients
Contributions are welcome. Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature-name - Commit your changes following Conventional Commits
- Push the branch and open a Pull Request
Ensure your changes compile without errors (pnpm build) before submitting.
This project is licensed under the MIT License. See the LICENSE file for details.