A comprehensive RESTful API for managing users and subscriptions with JWT authentication, subscription tracking, and renewal reminders.
- ✅ User Authentication (Sign Up, Sign In, Sign Out)
- ✅ Complete User Management (CRUD operations)
- ✅ Subscription Management with full lifecycle
- ✅ Subscription Status Tracking (Active, Cancelled, Expired)
- ✅ Upcoming Renewal Alerts
- ✅ JWT-based Authorization
- ✅ Input Validation with class-validator
- ✅ SQLite Database with TypeORM
- ✅ Password Encryption with bcryptjs
- ✅ Comprehensive Error Handling
- Framework: NestJS 11.0
- Database: SQLite with TypeORM
- Authentication: JWT (Passport)
- Validation: class-validator, class-transformer
- Security: bcryptjs for password hashing
- Testing: Jest & Supertest
src/
├── auth/ # Authentication module
│ ├── auth.controller.ts
│ ├── auth.service.ts
│ ├── auth.module.ts
│ ├── jwt.strategy.ts
│ ├── jwt-auth.guard.ts
│ └── dto/
│ └── auth.dto.ts
├── users/ # Users module
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── users.module.ts
│ └── dto/
│ └── user.dto.ts
├── subscriptions/ # Subscriptions module
│ ├── subscriptions.controller.ts
│ ├── subscriptions.service.ts
│ ├── subscriptions.module.ts
│ └── dto/
│ └── subscription.dto.ts
├── entities/ # Database entities
│ ├── user.entity.ts
│ └── subscription.entity.ts
├── app.module.ts
├── app.controller.ts
├── app.service.ts
└── main.ts
- Clone and install dependencies:
npm install- Start the development server:
npm run start:devThe API will be available at http://localhost:3000/api/v1
- Build for production:
npm run build- Run tests:
npm run test # Run unit tests
npm run test:e2e # Run e2e tests
npm run test:watch # Run tests in watch mode/api/v1
POST /auth/sign-up
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123",
"firstName": "John",
"lastName": "Doe"
}
Response (201):
{
"id": "uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}POST /auth/sign-in
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
Response (200):
{
"id": "uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}POST /auth/sign-out
Authorization: Bearer <access_token>
Response (200):
{
"message": "Logged out successfully"
}GET /users
Response (200):
[
{
"id": "uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2026-04-21T21:40:34.000Z",
"updatedAt": "2026-04-21T21:40:34.000Z"
}
]GET /users/:id
Response (200):
{
"id": "uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2026-04-21T21:40:34.000Z",
"updatedAt": "2026-04-21T21:40:34.000Z"
}POST /users
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123",
"firstName": "John",
"lastName": "Doe"
}
Response (201):
{
"id": "uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2026-04-21T21:40:34.000Z",
"updatedAt": "2026-04-21T21:40:34.000Z"
}PUT /users/:id
Authorization: Bearer <access_token>
Content-Type: application/json
{
"firstName": "Jonathan",
"lastName": "Doe-Smith",
"password": "newpassword123" // Optional
}
Response (200):
{
"id": "uuid",
"email": "user@example.com",
"firstName": "Jonathan",
"lastName": "Doe-Smith",
"createdAt": "2026-04-21T21:40:34.000Z",
"updatedAt": "2026-04-21T21:40:35.000Z"
}DELETE /users/:id
Authorization: Bearer <access_token>
Response (200):
{
"message": "User deleted successfully"
}GET /subscriptions
Response (200):
[
{
"id": "uuid",
"name": "Netflix",
"description": "Streaming service",
"cost": 15.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-05-22T00:00:00.000Z",
"status": "active",
"cancelledAt": null,
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:35.000Z"
}
]GET /subscriptions/:id
Response (200):
{
"id": "uuid",
"name": "Netflix",
"description": "Streaming service",
"cost": 15.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-05-22T00:00:00.000Z",
"status": "active",
"cancelledAt": null,
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:35.000Z"
}GET /subscriptions/user/:userId
Response (200):
[
{
"id": "uuid",
"name": "Netflix",
"description": "Streaming service",
"cost": 15.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-05-22T00:00:00.000Z",
"status": "active",
"cancelledAt": null,
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:35.000Z"
}
]POST /subscriptions
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "Netflix",
"description": "Streaming service",
"cost": 15.99,
"startDate": "2026-01-01",
"renewalDate": "2026-05-22"
}
Response (201):
{
"id": "uuid",
"name": "Netflix",
"description": "Streaming service",
"cost": 15.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-05-22T00:00:00.000Z",
"status": "active",
"cancelledAt": null,
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:35.000Z"
}PUT /subscriptions/:id
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "Netflix Premium",
"cost": 19.99,
"description": "Updated streaming service",
"renewalDate": "2026-06-15"
}
Response (200):
{
"id": "uuid",
"name": "Netflix Premium",
"description": "Updated streaming service",
"cost": 19.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-06-15T00:00:00.000Z",
"status": "active",
"cancelledAt": null,
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:36.000Z"
}PUT /subscriptions/:id/cancel
Authorization: Bearer <access_token>
Response (200):
{
"id": "uuid",
"name": "Netflix",
"description": "Streaming service",
"cost": 15.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-05-22T00:00:00.000Z",
"status": "cancelled",
"cancelledAt": "2026-04-21T21:40:35.951Z",
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:36.000Z"
}GET /subscriptions/upcoming-renewals?days=30
Response (200):
[
{
"id": "uuid",
"name": "Netflix Premium",
"description": "Streaming service",
"cost": 19.99,
"startDate": "2026-01-01T00:00:00.000Z",
"renewalDate": "2026-05-22T00:00:00.000Z",
"status": "active",
"cancelledAt": null,
"userId": "uuid",
"createdAt": "2026-04-21T21:40:35.000Z",
"updatedAt": "2026-04-21T21:40:35.000Z"
}
]DELETE /subscriptions/:id
Authorization: Bearer <access_token>
Response (200):
{
"message": "Subscription deleted successfully"
}The API uses JWT (JSON Web Tokens) for authentication. Include the token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...The API returns appropriate HTTP status codes and error messages:
{
"statusCode": 400,
"message": "Email already exists",
"error": "Conflict"
}Common HTTP Status Codes:
200: OK201: Created400: Bad Request401: Unauthorized403: Forbidden404: Not Found409: Conflict500: Internal Server Error
- Email: Must be a valid email format and unique
- Password: Minimum 6 characters
- FirstName: Required, must be a string
- LastName: Required, must be a string
- Name: Required, must be a string
- Cost: Required, must be a number
- StartDate: Required, must be a valid date (YYYY-MM-DD)
- Renewal Date: Required, must be a valid date after start date
- Description: Optional
id: UUID (Primary Key)email: String (Unique)password: String (Hashed with bcryptjs)firstName: StringlastName: StringcreatedAt: DateTime (Auto-generated)updatedAt: DateTime (Auto-generated)subscriptions: One-to-Many relationship with Subscription
id: UUID (Primary Key)name: Stringdescription: String (Optional)cost: Decimal (10,2)startDate: DateTimerenewalDate: DateTimestatus: Enum (active, cancelled, expired)cancelledAt: DateTime (Optional)userId: UUID (Foreign Key)createdAt: DateTime (Auto-generated)updatedAt: DateTime (Auto-generated)user: Many-to-One relationship with User
Run the comprehensive test suite:
# Run the test script
./test-api.sh
# Output shows:
# ✓ All 15 tests passedThe test suite covers:
- User Authentication (Sign Up, Sign In, Sign Out)
- User Management (Create, Read, Update, Delete)
- Subscription Management (Create, Read, Update, Cancel, Delete)
- Subscription Filtering (By User, Upcoming Renewals)
- Error Handling and Validation
Create a .env file (optional - defaults are provided):
PORT=3000
JWT_SECRET=your-secret-key-change-in-production
NODE_ENV=development- JWT Secret: Change the default JWT secret in production
- Passwords: All passwords are hashed using bcryptjs with salt rounds of 10
- Database: SQLite is suitable for development; use PostgreSQL for production
- CORS: Add CORS configuration as needed for your frontend
- Rate Limiting: Consider adding rate limiting middleware in production
- Email notifications for upcoming renewals
- Subscription categories and tags
- Budget tracking and analytics
- Recurring payment management
- Multi-currency support
- Mobile app support
- PostgreSQL support
- Docker containerization
- CI/CD pipeline
- API documentation with Swagger/OpenAPI
npm run start:devnpm run build
npm run start:prodrm -f subscription-tracker.db
npm run start:devPORT=3001 npm run start:devSign in again to get a new token. Default token expiry is 1 day.
UNLICENSED
For issues or questions, please refer to the test suite (test-api.sh) for usage examples.