A production-ready fullstack starter boilerplate built with:
- Backend: NestJS, PostgreSQL, Prisma, JWT Auth, RBAC, Swagger
- Frontend: Next.js App Router, TypeScript, Bun, shadcn/ui, Tailwind CSS, TanStack Query, Zustand, Axios
- Database: PostgreSQL via Docker Compose
- Auth: Access token + refresh token with secure cookie flow
- Roles:
SUPER_ADMIN,ADMIN,USER
fullstack-starter/
├── backend/
│ ├── prisma/
│ │ ├── schema.prisma
│ │ └── seed.ts
│ ├── src
│ │ ├── common/
│ │ │ ├── decorators/
│ │ │ ├── filters/
│ │ │ ├── guards/
│ │ │ ├── interceptors/
│ │ │ └── types/
│ │ ├── config/
│ │ ├── database/
│ │ ├── modules/
│ │ │ ├── admin/
│ │ │ ├── auth/
│ │ │ └── users/
│ │ ├── swagger/
│ │ ├── app.module.ts
│ │ └── main.ts
│ ├── .env.example
│ └── package.json
│
├── frontend/
│ ├── app/
│ │ │
│ │ ├── (marketing)/
│ │ ├── admin/
│ │ ├── user/
│ │ ├── forbidden/
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── providers.tsx
│ ├── components/
│ │ ├── layout/
│ │ └── ui/
│ ├── constants/
│ ├── features/
│ │ ├── auth/
│ │ └── users/
│ ├── lib/
│ └── types/
│ ├── .env.example
│ └── package.json
│
├── docker-compose.yml
├── .gitignore
└── README.md- NestJS modular architecture
- PostgreSQL database using Docker
- Prisma ORM
- JWT authentication
- Access token returned to frontend
- Refresh token stored in httpOnly cookie
- Global JWT guard
@Public()decorator for public routes- Role-based access control with
@Roles() - Roles:
SUPER_ADMINADMINUSER
- User management module
- Admin dashboard module
- Swagger API documentation
- Global validation pipe
- Global response interceptor
- Global exception filter
- Next.js App Router
- Bun as package manager
- Feature-based architecture
- shadcn/ui components
- shadcn Sidebar layout
- Tailwind CSS
- TanStack Query
- Axios API client
- Zustand auth store
- React Hook Form + Zod
- Role-based route redirects
- Separate route areas:
- Marketing/Auth
- User portal
- Admin portal
/ Landing page
/login Login page
/register Register page
/forgot-password Forgot password page
/otp OTP page/user/dashboard
/user/profile
/user/settingsOnly normal USER accounts should access this area.
/admin/dashboard
/admin/users
/admin/settingsBoth SUPER_ADMIN and ADMIN can access the admin area.
Some actions are only visible/available to SUPER_ADMIN.
SUPER_ADMIN
- Access admin dashboard
- View users
- Update user roles
- Activate/deactivate users
- Access super-admin-only UI/actions
ADMIN
- Access admin dashboard
- View users
- Cannot update user roles
- Cannot activate/deactivate users
USER
- Access user dashboard
- Access own profile/settings
- Cannot access admin areaThese accounts can be seeded for local testing.
Email: superadmin@example.com
Password: 123456789
Role: SUPER_ADMINEmail: admin@example.com
Password: 123456789
Role: ADMINEmail: user@example.com
Password: 1223456789
Role: USERMake sure you have installed:
- Node.js
- Docker
- Docker Compose
- Bun
- npm
Backend uses npm in this starter.
Frontend uses Bun.
Create:
backend/.envUse this example:
NODE_ENV=development
PORT=4000
DATABASE_URL="postgresql://starter_user:starter_password@localhost:5432/starter_db?schema=public"
FRONTEND_URL="http://localhost:3000"
JWT_ACCESS_SECRET="replace-with-strong-access-secret"
JWT_REFRESH_SECRET="replace-with-strong-refresh-secret"
JWT_ACCESS_EXPIRES_IN="15m"
JWT_REFRESH_EXPIRES_IN="7d"
COOKIE_DOMAIN="localhost"
Create:
frontend/.env.localUse this example:
NEXT_PUBLIC_APP_NAME="Fullstack Starter"
NEXT_PUBLIC_API_URL="http://localhost:4000/api/v1"
NEXT_PUBLIC_APP_URL="http://localhost:3000"From the project root:
docker compose up -dThis starts PostgreSQL at:
localhost:5432cd backend
npm installnpx prisma migrate dev --name initThis will:
1. Create database tables
2. Generate Prisma Clientnpx prisma db seedOr:
npm run db:seedThis creates the test users.
npm run devBackend runs at:
http://localhost:4000Swagger docs:
http://localhost:4000/api/docsHealth check:
http://localhost:4000/api/v1/healthOpen a new terminal:
cd frontend
bun installbun devFrontend runs at:
http://localhost:3000cd backendRun development server:
npm run devBuild:
npm run buildGenerate Prisma Client:
npx prisma generateCreate migration:
npx prisma migrate dev --name migration_nameOpen Prisma Studio:
npx prisma studioSeed database:
npx prisma db seedReset local database:
npx prisma migrate resetcd frontendRun development server:
bun devBuild:
bun run buildType check:
bun run type-checkAdd a package:
bun add package-nameAdd shadcn component:
bunx shadcn@latest add component-nameFrontend calls:
POST /api/v1/auth/loginBackend returns:
user
accessTokenThe refresh token is stored in an httpOnly cookie.
The frontend stores the access token in Zustand and attaches it to requests:
Authorization: Bearer ACCESS_TOKENWhen an API request returns 401, Axios tries:
POST /api/v1/auth/refreshIf the refresh token cookie is valid, the backend returns a new access token.
Frontend calls:
POST /api/v1/auth/logoutBackend revokes the current refresh session and clears the cookie.
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
POST /api/v1/auth/logout-all
GET /api/v1/auth/meGET /api/v1/users/me
PATCH /api/v1/users/me
GET /api/v1/users
GET /api/v1/users/:id
PATCH /api/v1/users/:id/role
PATCH /api/v1/users/:id/statusGET /api/v1/admin/overview
GET /api/v1/admin/statsThe frontend follows a feature-based structure.
features/auth/
├── components/
├── hooks/
├── schemas/
├── services/
├── stores/
└── types.tsfeatures/users/
├── components/
├── hooks/
├── services/
└── types.tsReusable UI primitives go in:
components/ui/Feature-specific logic goes in:
features/{feature-name}/Route-specific components go inside:
app/.../_components/This avoids creating a large global component folder.
Used for:
/
/login
/register
/forgot-password
/otpFolder:
app/(marketing)/Used for:
/user/*Folder:
app/user/Uses:
components/layout/user/Used for:
/admin/*Folder:
app/admin/Uses:
components/layout/admin/The admin layout uses shadcn Sidebar components:
SidebarProvider
Sidebar
SidebarHeader
SidebarContent
SidebarGroup
SidebarMenu
SidebarMenuItem
SidebarFooter
SidebarRail
SidebarInset
SidebarTriggerFrontend route protection uses:
proxy.tsThe proxy protects:
/user/*
/admin/*It checks a frontend-readable auth flag cookie:
starter_authThis is only for redirect UX.
Real security is always enforced by the backend using JWT guards and RBAC.
Backend routes are protected globally by JwtAuthGuard.
Public routes use:
@Public()Role-protected routes use:
@Roles(Role.SUPER_ADMIN)or:
@Roles(Role.SUPER_ADMIN, Role.ADMIN)Example:
@UseGuards(RolesGuard)
@Roles(Role.SUPER_ADMIN)
@Patch(':id/role')
updateRole() {
// Only SUPER_ADMIN can access
}Main models:
User
SessionMain enum:
Role
- SUPER_ADMIN
- ADMIN
- USERSession stores refresh-token sessions and allows logout/session revocation.
Swagger is available at:
http://localhost:4000/api/docsUse Swagger to test:
Auth
Users
Admin
HealthFor protected endpoints:
- Login using
/auth/login - Copy the
accessToken - Click
Authorize - Enter:
Bearer YOUR_ACCESS_TOKENDo not commit real environment files.
Ignored files include:
.env
.env.local
node_modules/
dist/
.next/
out/Commit example files:
backend/.env.example
frontend/.env.examplecd backend
npm run buildcd frontend
bun run type-check
bun run buildFrom the project root:
git init
git add .
git commit -m "Initial fullstack starter setup"
git branch -M main
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPOSITORY.git
git push -u origin mainPossible next improvements:
- Dockerfile for backend deployment
- Dockerfile for frontend deployment
- Production Docker Compose setup
- Email verification
- Forgot password flow
- OTP flow
- Rate limiting for auth endpoints
- Request logging
- Audit logs
- File upload module
- Better Swagger DTO response wrappers
- Unit and e2e tests
- CI/CD pipeline
- Deployment guide
This project is intended as a starter boilerplate. Update the license based on your project requirements.