A production-ready GraphQL API boilerplate with built-in database sharding, authentication, and Docker deployment.
- Runtime: Node.js v22+
- Language: TypeScript
- Framework: Fastify 5 + Mercurius (GraphQL)
- Database: PostgreSQL with Prisma ORM
- Sharding: prisma-sharding
- Authentication: AuthLite + Refresh Token Rotation
- Caching: Resilient Redis (Graceful Degradation)
- Queue: BullMQ with Dashboard
- Security: Helmet, Rate Limiting, CORS, OTP
- Deployment: Docker + Nginx load balancer
- Testing: Vitest
This boilerplate uses Fastify + Mercurius instead of Express + Apollo Server for:
| Feature | Fastify + Mercurius | Express + Apollo |
|---|---|---|
| Performance | π Excellent | Good |
| Security defaults | β Strong | Manual setup |
| Schema validation | β Built-in | Manual |
| Request overhead | Lower | Higher |
| Async/await | Native | Middleware-based |
git clone <repository_url>
cd graphql-prisma-postgres-ts-boilerplate
yarn docker:dev # Start development environment (uses .env.docker)
yarn docker:migrate # Run database migrationsOpen http://localhost:3001/graphql
Prerequisites: Node.js v22+, PostgreSQL, Redis running locally
Note: Ensure your
.envfile useslocalhostforDATABASE_URLandREDIS_HOST.
git clone <repository_url>
cd graphql-prisma-postgres-ts-boilerplate
yarn install
yarn db:update
yarn dev # Start development server- GraphQL API: http://localhost:4200/graphql
- Queue Dashboard: http://localhost:4200/admin/queues (Dev only)
This boilerplate comes hardened by default:
- Helmet: Adds secure HTTP headers (CSP, HSTS, X-Frame-Options, etc.)
- Rate Limiting: Hybrid IP/User-based limiting with Redis backing (1000 req/min auth, 60 req/min anon)
- CORS: Strict origin matching (no wildcard subdomains)
- OTP: Cryptographically secure 6-digit codes (
crypto.randomInt) - GraphQL:
- Query Depth: Max depth of 10 to prevent nested attacks
- Introspection: Disabled in production (
NODE_ENV=production)
Implements a secure Access + Refresh Token strategy:
- Login/Signup: Returns
{ token, refreshToken, user }token: Short-lived (15m), sent inAuthorization: BearerheaderrefreshToken: Long-lived (7d), used to get new tokens
- Refresh: Call
refreshTokens(refreshToken)mutation to get a new pair.- Old refresh token is revoked (Reuse Detection).
- Logout:
logoutorlogoutAllto revoke tokens. - Storage: Refresh tokens are stored in Redis with whitelist for validation.
Asynchronous tasks are handled by BullMQ on Redis.
- Workers: Located in
src/queues/*.queue.ts - Dashboard: Visit
/admin/queuesto monitor jobs (Auto-disabled in Production) - Resilience:
- Automatic retries with exponential backoff
- Dead Letter Queue (DLQ) for failed jobs
- Graceful shutdown of workers
- Redis: The app starts even if Redis is down. Caching and Rate Limiting degrade gracefully to in-memory fallback.
- Database: Circuit breakers for extensive sharding operations.
This project uses Vitest for unit testing, focusing on GraphQL resolvers and business logic. The tests are configured with a custom reporter for clean, action-oriented output.
yarn testOutput Example:
Get user : SUCCESS
Create a new user : SUCCESS
Login user : SUCCESS
...
--- Test Summary ---
Total: 25
Passed: 25
Failed: 0
The best GraphQL IDE experience! Open in your browser:
https://studio.apollographql.com/sandbox/explorer
Then connect to: http://localhost:4200/graphql
Open: http://localhost:4200/graphiql
# Test introspection
curl -X POST http://localhost:4200/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}'
# Test a query
curl -X POST http://localhost:4200/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ me { id email } }"}'
# With authentication
curl -X POST http://localhost:4200/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"query": "{ me { id email username } }"}'- Altair - Feature-rich GraphQL client
- Insomnia - REST & GraphQL client
- Postman - API testing platform
| Command | Description |
|---|---|
yarn dev |
Start local dev server with hot-reload |
yarn docker:dev |
Start Docker dev environment |
yarn docker:dev:build |
Rebuild and start Docker dev |
yarn docker:dev:logs |
View Docker dev logs |
yarn docker:dev:down |
Stop Docker dev containers |
| Command | Description |
|---|---|
yarn build |
Build production bundle |
yarn start |
Start production server |
yarn docker:prod |
Start Docker production environment |
yarn docker:prod:build |
Rebuild and start Docker prod |
yarn docker:prod:logs |
View Docker prod logs |
yarn docker:prod:down |
Stop Docker prod containers |
| Command | Description |
|---|---|
yarn db:udpate |
It generates Prisma Client types and migrates all shards |
yarn db:studio |
Open Prisma Studio for all shards |
yarn test:shards |
Test shard connectivity |
yarn docker:migrate |
Run migrations in Docker |
| Command | Description |
|---|---|
yarn generate |
Generate GraphQL types |
yarn docker:clean |
Remove all Docker volumes and images |
yarn docker:sh |
Shell into app container |
βββ Dockerfile # Multi-stage (dev + prod stages)
βββ docker-compose.yml # Development environment (uses .env.docker)
βββ docker-compose.prod.yml # Production environment (uses .env)
βββ .env # Local/Prod Configuration
βββ nginx/nginx.conf # Load balancer
The single Dockerfile contains both development and production stages:
- Development:
target: development- hot-reload, dev dependencies - Production:
target: production- optimized, minimal image
Built-in horizontal sharding across multiple PostgreSQL instances:
import { getShardForUser, findUserAcrossShards } from '@/config/prisma'
// Get shard for a specific user
const client = getShardForUser(userId)
const user = await client.user.findUnique({ where: { id: userId } })
// Find user across all shards
const { result } = await findUserAcrossShards(async (client) =>
client.user.findFirst({ where: { email: 'user@example.com' } })
)Use the graphql-prisma-select package to fetch only the fields required by the client. This helps resolve the over-fetching problem and improves database performance.
import { prismaSelect } from 'graphql-prisma-select'
const resolvers = {
Query: {
users: async (parent, args, context, info) => {
const select = prismaSelect(info)
// Equivalent to: prisma.user.findMany({ select: { id: true, email: true, ... } })
return context.prisma.user.findMany({
...args, // pagination, filtering, etc.
select,
})
},
user: async (parent, { id }, context, info) => {
const select = prismaSelect(info)
return context.prisma.user.findUnique({
where: { id },
select,
})
},
},
}src/
βββ modules/ # Feature modules (auth, user, etc.)
β βββ <module>/
β βββ graphql/ # GraphQL schema
β βββ resolvers/ # Business logic
β βββ tests/ # Unit tests
βββ config/ # Prisma, Redis, AuthLite, Tokens, Queues
βββ middleware/ # Auth, CORS, Rate Limit
βββ queues/ # BullMQ definitions & workers
βββ guards/ # Authentication guards
βββ cache/ # Redis caching strategies
βββ errors/ # Error handling & formatters
βββ graphql/ # Scalars & base schema
βββ types/ # Generated TypeScript types
βββ utils/ # Shared utilities
βββ server.ts # Fastify entry point
services/storage/ # π¦ Storage Microservice
βββ src/
β βββ providers/ # S3, Cloudinary, ImageKit, Local
β βββ services/ # File, upload, folder, share-link logic
β βββ routes/ # REST API endpoints
β βββ server.ts # Express entry point
βββ README.md # Full API reference
This boilerplate uses dynamic project naming - your repository name becomes the project identifier, allowing multiple deployments on the same VPS without conflicts.
Setup:
- Run on VPS:
curl -fsSL https://raw.githubusercontent.com/safdar-azeem/graphql-prisma-postgres-ts-boilerplate/main/scripts/setup-vps.sh | bash - Set GitHub secrets:
VPS_HOST- Your VPS IP addressVPS_USERNAME- SSH username (usuallyroot)VPS_SSH_KEY- Private SSH key for authenticationGH_SECRET- GitHub Personal Access Token
- Push to
mainbranch β auto-deploys
See docs/5-setup-vps-deployment.md for detailed VPS setup.
| Issue | Solution |
|---|---|
| Server exits immediately | Check MFA_ENCRYPTION_KEY is set (32 chars) |
| Port already in use | Kill process: lsof -i :4200 then kill -9 <PID> |
| Docker build fails | Run yarn docker:clean and try again |
| Redis connection error | Ensure Redis is running: redis-server |
| GraphiQL not loading | Use Apollo Sandbox instead (see Testing section) |
- Getting Started Guide
- Docker Guide
- GraphQL API Guide
- Sharding Design
- VPS Setup
- Storage Service
- Storage Walkthrough
Fastify + Mercurius provides excellent performance for GraphQL applications:
- JIT Query Compilation - Queries are compiled for faster execution
- Automatic Loader Integration - Prevents N+1 query problems
- Query Parsing Cache - Validated queries are cached
- Low Request Overhead - Fastify's async architecture
For benchmarking, use:
autocannon -c30 -d10 http://localhost:4200/health