A production-ready SaaS application skeleton built with Fastify, TypeScript, Prisma, and PostgreSQL. This project provides a solid foundation for building multi-tenant SaaS applications with authentication, authorization, and organization management.
-
Authentication System
- User registration and login
- JWT-based access tokens
- Refresh token rotation with secure HttpOnly cookies
- Password hashing with bcrypt
- Token revocation support
-
Multi-Tenant Architecture
- Organization (Org) management
- Role-based access control (RBAC)
- Permission system
- User memberships across organizations
-
Modern Tech Stack
- Fastify for high-performance API
- TypeScript for type safety
- Prisma ORM with PostgreSQL adapter
- Redis for caching and job queues
- Docker Compose for local development
-
Security Best Practices
- HttpOnly cookies for refresh tokens
- JWT token expiration
- Password hashing
- Token rotation on refresh
- Audit logging support
- Runtime: Node.js with TypeScript
- Framework: Fastify 5.x
- Database: PostgreSQL 16
- ORM: Prisma 7.x
- Cache/Queue: Redis 7
- Authentication: JWT (JSON Web Tokens)
- Password Hashing: bcrypt
saas-skeleton/
βββ apps/
β βββ api/ # Main API application
β βββ src/
β β βββ modules/ # Feature modules
β β β βββ auth/ # Authentication routes & services
β β β βββ me/ # User profile endpoints
β β βββ plugins/ # Fastify plugins
β β β βββ prisma.ts # Database plugin
β β β βββ redis.ts # Redis plugin
β β β βββ authGuard.ts # JWT authentication guard
β β βββ common/ # Shared utilities
β β βββ server.ts # Application entry point
β βββ prisma/
β βββ schema.prisma # Database schema
β βββ migrations/ # Database migrations
βββ infra/
βββ docker-compose.yml # Local development services
- User: User accounts with email and password hash
- Org: Organizations (tenants) in the system
- Membership: User-organization-role relationships
- Role: Roles within organizations
- Permission: Granular permissions
- RolePermission: Role-permission mappings
- RefreshToken: Refresh token storage with rotation support
- AuditLog: Audit trail for user actions
- Job: Background job tracking
- Node.js 18+
- Docker and Docker Compose
- npm or yarn
-
Clone the repository
git clone <repository-url> cd saas-skeleton
-
Install dependencies
npm install cd apps/api npm install -
Start infrastructure services
docker-compose -f infra/docker-compose.yml up -d
-
Set up environment variables Create a
.envfile inapps/api/:DATABASE_URL="postgresql://postgres:postgres@localhost:5432/app" REDIS_URL="redis://localhost:6379" JWT_SECRET="your-secret-key-change-in-production" PORT=3001 NODE_ENV=development REFRESH_TOKEN_TTL_DAYS=7
-
Run database migrations
cd apps/api npx prisma migrate dev -
Seed the database
npm run seed
This creates a test user:
- Email:
a@a.com - Password:
123456
- Email:
-
Start the development server
npm run dev
The API will be available at http://localhost:3001
Login and receive access token. Refresh token is set as HttpOnly cookie.
Request:
{
"email": "a@a.com",
"password": "123456"
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Cookies: refresh_token (HttpOnly, 7 days)
Refresh access token using refresh token from cookie.
Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Cookies: New refresh_token (rotated)
Logout and revoke refresh token.
Response:
{
"ok": true
}Get current user information. Requires authentication.
Headers:
Authorization: Bearer <accessToken>
Response:
{
"id": "user-uuid",
"email": "user@example.com"
}Health check endpoint.
Response:
{
"ok": true
}-
Login
- User provides email and password
- Server validates credentials
- Creates JWT access token (short-lived)
- Creates refresh token (random string, not JWT)
- Stores refresh token hash in database
- Sets refresh token as HttpOnly cookie
- Returns access token in response body
-
Accessing Protected Routes
- Client includes access token in
Authorization: Bearer <token>header - Server validates JWT token
- If valid, request proceeds
- Client includes access token in
-
Refreshing Tokens
- Client sends refresh token from cookie
- Server validates token (exists, not revoked, not expired)
- Token Rotation: Deletes old refresh token, creates new one
- Issues new access token
- Sets new refresh token cookie
- Returns new access token
-
Logout
- Server revokes refresh token in database
- Clears refresh token cookie
# Development (with hot reload)
npm run dev
# Build for production
npm run build
# Start production server
npm start
# Run database migrations
npx prisma migrate dev
# Seed database
npm run seed
# Prisma Studio (database GUI)
npx prisma studio- Modules: Feature-based organization (
auth,me) - Plugins: Reusable Fastify plugins (Prisma, Redis, Auth Guard)
- Common: Shared utilities and error classes
- Prisma: Database schema and migrations
-
Fastify Plugin Encapsulation: Prisma plugin uses
fastify-pluginto break encapsulation, making it available across all route modules. -
Refresh Token Rotation: On each refresh, the old token is deleted and a new one is created for enhanced security.
-
HttpOnly Cookies: Refresh tokens are stored in HttpOnly cookies to prevent XSS attacks.
-
Token Hashing: Refresh tokens are hashed before storage in the database.
The docker-compose.yml provides:
- PostgreSQL 16: Main database
- Redis 7: Cache and job queue
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Required |
REDIS_URL |
Redis connection string | redis://localhost:6379 |
JWT_SECRET |
Secret for JWT signing | dev-secret |
PORT |
Server port | 3001 |
NODE_ENV |
Environment | development |
REFRESH_TOKEN_TTL_DAYS |
Refresh token expiration | 7 |
- β Passwords are hashed with bcrypt
- β Refresh tokens stored as HttpOnly cookies
- β JWT tokens have expiration
- β Refresh token rotation on use
- β Token revocation support
β οΈ ChangeJWT_SECRETin productionβ οΈ Use HTTPS in productionβ οΈ Setsecure: truefor cookies in production
- Add user registration endpoint
- Implement organization CRUD operations
- Add role and permission management
- Implement audit logging
- Add rate limiting
- Add request validation with Zod
- Add API documentation (Swagger/OpenAPI)
- Add unit and integration tests
- Add CI/CD pipeline
This is a skeleton project. Feel free to fork and customize for your needs.
ISC