Skip to content

Latest commit

 

History

History
574 lines (482 loc) · 11.6 KB

File metadata and controls

574 lines (482 loc) · 11.6 KB

Subscription Tracker API

A comprehensive RESTful API for managing users and subscriptions with JWT authentication, subscription tracking, and renewal reminders.

Features

  • ✅ 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

Tech Stack

  • Framework: NestJS 11.0
  • Database: SQLite with TypeORM
  • Authentication: JWT (Passport)
  • Validation: class-validator, class-transformer
  • Security: bcryptjs for password hashing
  • Testing: Jest & Supertest

Project Structure

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

Installation & Setup

  1. Clone and install dependencies:
npm install
  1. Start the development server:
npm run start:dev

The API will be available at http://localhost:3000/api/v1

  1. Build for production:
npm run build
  1. 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 Endpoints

Base URL

/api/v1

Authentication Endpoints

Sign Up

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..."
}

Sign In

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..."
}

Sign Out

POST /auth/sign-out
Authorization: Bearer <access_token>

Response (200):
{
  "message": "Logged out successfully"
}

Users Endpoints

Get All Users

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 User by ID

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"
}

Create User

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"
}

Update User

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 User

DELETE /users/:id
Authorization: Bearer <access_token>

Response (200):
{
  "message": "User deleted successfully"
}

Subscriptions Endpoints

Get All Subscriptions

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 Subscription by ID

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 User Subscriptions

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"
  }
]

Create Subscription

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"
}

Update Subscription

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"
}

Cancel Subscription

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 Upcoming Renewals

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 Subscription

DELETE /subscriptions/:id
Authorization: Bearer <access_token>

Response (200):
{
  "message": "Subscription deleted successfully"
}

Authentication

The API uses JWT (JSON Web Tokens) for authentication. Include the token in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Error Handling

The API returns appropriate HTTP status codes and error messages:

{
  "statusCode": 400,
  "message": "Email already exists",
  "error": "Conflict"
}

Common HTTP Status Codes:

  • 200: OK
  • 201: Created
  • 400: Bad Request
  • 401: Unauthorized
  • 403: Forbidden
  • 404: Not Found
  • 409: Conflict
  • 500: Internal Server Error

Validation Rules

User Sign Up/Create

  • 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

Subscription Create

  • 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

Database Models

User Entity

  • id: UUID (Primary Key)
  • email: String (Unique)
  • password: String (Hashed with bcryptjs)
  • firstName: String
  • lastName: String
  • createdAt: DateTime (Auto-generated)
  • updatedAt: DateTime (Auto-generated)
  • subscriptions: One-to-Many relationship with Subscription

Subscription Entity

  • id: UUID (Primary Key)
  • name: String
  • description: String (Optional)
  • cost: Decimal (10,2)
  • startDate: DateTime
  • renewalDate: DateTime
  • status: 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

Testing

Run the comprehensive test suite:

# Run the test script
./test-api.sh

# Output shows:
# ✓ All 15 tests passed

The 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

Environment Variables

Create a .env file (optional - defaults are provided):

PORT=3000
JWT_SECRET=your-secret-key-change-in-production
NODE_ENV=development

Security Notes

  1. JWT Secret: Change the default JWT secret in production
  2. Passwords: All passwords are hashed using bcryptjs with salt rounds of 10
  3. Database: SQLite is suitable for development; use PostgreSQL for production
  4. CORS: Add CORS configuration as needed for your frontend
  5. Rate Limiting: Consider adding rate limiting middleware in production

Future Enhancements

  • 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

Deployment

Development

npm run start:dev

Production

npm run build
npm run start:prod

Troubleshooting

Database is read-only

rm -f subscription-tracker.db
npm run start:dev

Port already in use

PORT=3001 npm run start:dev

JWT token expired

Sign in again to get a new token. Default token expiry is 1 day.

License

UNLICENSED

Support

For issues or questions, please refer to the test suite (test-api.sh) for usage examples.