A robust NestJS REST API for a social blogging platform with JWT authentication, user management, posts, comments, and groups. Built with TypeScript, Drizzle ORM, and PostgreSQL.
- Authentication & Authorization - JWT-based auth with secure password hashing (bcrypt)
- User Management - Complete CRUD operations with profile support
- Posts & Comments - Create, read, update, and delete blog posts and comments
- Groups - Create and manage groups with member roles (owner/member)
- Profile System - Rich user profiles with metadata (bio, avatar, social links, preferences)
- Type-Safe ORM - Drizzle ORM with full TypeScript support
- API Documentation - Auto-generated Swagger/OpenAPI documentation
- Validation - Class-validator with comprehensive DTO validation
- Error Handling - Global exception filters with detailed error responses
- Rate Limiting - Built-in throttling (100 requests/minute)
- Health Checks - Database health monitoring endpoint
- Security - Password hashing, JWT tokens, ownership guards
- Framework: NestJS 11.0.1
- Language: TypeScript 5.7.3
- Database: PostgreSQL
- ORM: Drizzle ORM
- Authentication: JWT (jsonwebtoken)
- Validation: class-validator, class-transformer
- Documentation: Swagger/OpenAPI (@nestjs/swagger)
- Security: bcrypt, @nestjs/throttler
- Health Checks: @nestjs/terminus
- Node.js 22
- PostgreSQL 17
- pnpm
- Clone the repository
git clone https://github.com/juniorenv/nestjs-social-api.git
cd nestjs-social-api- Install dependencies
pnpm install- Environment configuration
Create a .env file in the root directory based on .env.example:
cp .env.example .envFor Docker deployment (recommended):
# Database Configuration
POSTGRES_USER=pguser
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_DB=nestjsdrizzle
POSTGRES_PORT=5432
# API Configuration
API_PORT=3000
NODE_ENV=development
# JWT Configuration
JWT_SECRET=your_jwt_secret_here
JWT_EXPIRATION=3600The DATABASE_URL will be automatically constructed by the application using the individual PostgreSQL variables above.
For local PostgreSQL (without Docker):
Add the DATABASE_URL to your .env file:
# Database Configuration
DATABASE_URL=postgresql://pguser:your_secure_password_here@localhost:5432/nestjsdrizzle
# Or use individual variables (the app will construct the URL)
POSTGRES_USER=pguser
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_DB=nestjsdrizzle
POSTGRES_PORT=5432
# API Configuration
API_PORT=3000
NODE_ENV=development
# JWT Configuration
JWT_SECRET=your_jwt_secret_here
JWT_EXPIRATION=3600- Database setup
Generate migration:
pnpm db:generateRun migrations:
pnpm db:migrateSeed database (optional):
pnpm db:seed- Start the application
Development mode:
pnpm devProduction mode:
pnpm build
pnpm start:prodThe project includes Docker Compose configuration for easy deployment:
-
Ensure Docker and Docker Compose are installed
-
Configure environment
cp .env.example .env
# Edit .env with your configuration- Start all services
docker compose up -dThis will start:
- PostgreSQL database container
- NestJS API container
- View logs
docker compose logs -f- Stop services
docker compose down- Stop and remove volumes (database data)
docker compose down -vOnce the application is running, access the interactive API documentation at:
http://localhost:3000/docs
The Swagger UI provides:
- Complete endpoint documentation
- Request/response schemas
- Try-it-out functionality
- Authentication support
POST /auth/signup
Content-Type: application/json
{
"name": "John Doe",
"email": "john.doe@example.com",
"password": "SecurePass123!"
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "uuid",
"name": "John Doe",
"email": "john.doe@example.com",
"createdAt": "2025-02-10T12:00:00.000Z",
"updatedAt": "2025-02-10T12:00:00.000Z"
}
}POST /auth/signin
Content-Type: application/json
{
"email": "john.doe@example.com",
"password": "SecurePass123!"
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Include the JWT token in the Authorization header for protected endpoints:
GET /users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /auth/signup |
No | Register new user |
| POST | /auth/signin |
No | Login user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /users |
No | Create user (alternative to signup) |
| GET | /users/:userId |
No | Get user by ID |
| GET | /users/me |
Yes | Get current user profile |
| PATCH | /users/me |
Yes | Update current user |
| DELETE | /users/me |
Yes | Delete current user |
| PUT | /users/me/password |
Yes | Change password |
| POST | /users/me/profile |
Yes | Create profile |
| PATCH | /users/me/profile |
Yes | Update profile |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /posts |
Yes | Create post |
| GET | /posts/:postId |
No | Get post by ID |
| PATCH | /posts/:postId |
Yes* | Update post |
| DELETE | /posts/:postId |
Yes* | Delete post |
| POST | /posts/:postId/comments |
Yes | Add comment to post |
*Only the post author can update/delete
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /comments/:commentId |
No | Get comment by ID |
| PATCH | /comments/:commentId |
Yes* | Update comment |
| DELETE | /comments/:commentId |
Yes* | Delete comment |
*Only the comment author can update/delete
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /groups |
Yes | Create group |
| GET | /groups/:groupId |
No | Get group by ID |
| PATCH | /groups/:groupId |
Yes** | Update group |
| DELETE | /groups/:groupId |
Yes** | Delete group |
| POST | /groups/:groupId/join |
Yes | Join group |
| DELETE | /groups/:groupId/leave |
Yes | Leave group |
| DELETE | /groups/:groupId/members/:userId |
Yes** | Remove member |
**Only group owner can perform these actions
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Check system health |
curl -X POST http://localhost:3000/posts \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "My First Post",
"content": "This is the content of my post about TypeScript and NestJS."
}'curl http://localhost:3000/posts/post-uuid-hereResponse:
{
"id": "uuid",
"title": "My First Post",
"content": "This is the content...",
"createdAt": "2025-02-10T12:00:00.000Z",
"updatedAt": "2025-02-10T12:00:00.000Z",
"author": {
"id": "uuid",
"name": "John Doe"
},
"comments": [
{
"id": "uuid",
"text": "Great post!",
"createdAt": "2025-02-10T12:30:00.000Z",
"updatedAt": "2025-02-10T12:30:00.000Z",
"author": {
"id": "uuid",
"name": "Jane Smith"
}
}
]
}curl -X POST http://localhost:3000/users/me/profile \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"bio": "Full-stack developer passionate about TypeScript",
"avatar": "https://i.pravatar.cc/300",
"phone": "+5577996483728",
"location": "Brazil",
"website": "https://johndoe.dev",
"socialLinks": {
"twitter": "https://twitter.com/johndoe",
"linkedin": "https://linkedin.com/in/johndoe",
"github": "https://github.com/johndoe"
},
"preferences": {
"theme": "dark",
"notifications": true,
"language": "en",
"emailNotifications": true,
"timezone": "America/New_York"
},
"occupation": "Software Engineer",
"company": "Tech Corp",
"skills": ["TypeScript", "React", "Node.js"]
}
}'# Create group
curl -X POST http://localhost:3000/groups \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "TypeScript",
"description": "A community for TypeScript enthusiasts"
}'
# Join group
curl -X POST http://localhost:3000/groups/group-uuid-here/join \
-H "Authorization: Bearer YOUR_JWT_TOKEN"src/
βββ auth/ # Authentication module
β βββ dto/ # Auth DTOs (SignIn, SignUp)
β βββ interfaces/ # JWT payload interface
β βββ auth.controller.ts
β βββ auth.service.ts
β βββ auth.guard.ts # JWT authentication guard
β βββ auth.module.ts
β
βββ user/ # User management module
β βββ dto/ # User DTOs
β βββ user.controller.ts
β βββ user.service.ts
β βββ user.module.ts
β
βββ post/ # Post management module
β βββ dto/
β βββ post.controller.ts
β βββ post.service.ts
β βββ post.module.ts
β
βββ comment/ # Comment module
β βββ dto/
β βββ comment.controller.ts
β βββ comment.service.ts
β βββ comment.module.ts
β
βββ group/ # Group management module
β βββ dto/
β βββ group.controller.ts
β βββ group.service.ts
β βββ group.module.ts
β
βββ drizzle/ # Database layer
β βββ schema/ # Drizzle schemas
β β βββ users.schema.ts
β β βββ posts.schema.ts
β β βββ comments.schema.ts
β β βββ groups.schema.ts
β β βββ profileInfo.schema.ts
β β βββ schema.ts
β βββ types/ # Type definitions
β βββ drizzle.module.ts
β βββ migrate.ts # Migration runner
β βββ seed.ts # Database seeder
β
βββ common/ # Shared utilities
β βββ constants/ # Swagger examples, etc.
β βββ decorators/ # Custom decorators
β βββ filters/ # Exception filters
β βββ guards/ # Authorization guards
β βββ types/ # Express type extensions
β
βββ health/ # Health check module
β βββ health.controller.ts
β βββ health.module.ts
β βββ drizzle-health.indicator.ts
β
βββ app.module.ts # Root module
βββ main.ts # Application entry point
- Passwords are hashed using bcrypt (10 salt rounds)
- Password requirements: 8-50 characters
- Passwords are never returned in API responses (using
@Exclude()decorator)
- Tokens expire after 1 hour (configurable)
- Secure token validation with error handling
- Tokens required for protected endpoints
- Resource Ownership Guard: Users can only modify their own posts/comments
- Group Ownership Guard: Only group owners can update/delete groups or remove members
- Role-based access control in groups (owner vs member)
- 100 requests per minute per IP
- Prevents brute-force attacks
- All request bodies validated using class-validator
- UUID validation for path parameters
- Field length restrictions
- Email format validation
- URL validation for profile links
The project includes a comprehensive seeder for development:
pnpm run db:seedThis creates:
- 50 users with hashed passwords
- 50 profile records with complete metadata
- 50 posts
- 50 comments
- 3 groups (TypeScript, Rust, GO)
- User-group memberships
{
"start": "nest start",
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"build": "nest build",
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx src/drizzle/migrate.ts",
"db:seed": "tsx src/drizzle/seed.ts",
"db:studio": "drizzle-kit studio"
}- AuthGuard: Validates JWT tokens, attaches user to request
- ResourceOwnershipGuard: Ensures users can only modify their own resources
- GroupOwnershipGuard: Restricts group management to owners
@ResourceType(): Specifies resource type for ownership validation@AtLeastOneField(): Validates that partial updates have at least one field
plainToInstance(): Ensures response DTOs exclude sensitive fields@Exclude(): Prevents password from being serialized
- DatabaseExceptionFilter: Catches and formats database errors
- Detailed error responses with timestamps and request paths
- Type guards for Drizzle/PostgreSQL error detection
The API provides consistent error responses:
{
"statusCode": 400,
"timestamp": "2026-02-10T12:00:00.000Z",
"path": "/users/me",
"message": {
"message": ["Invalid email format", "..."],
"error": "Bad Request"
}
}Common error codes:
400- Bad Request (validation errors)401- Unauthorized (invalid/missing token)403- Forbidden (insufficient permissions)404- Not Found409- Conflict (duplicate email/group name)500- Internal Server Error503- Service Unavailable (database connection)



