NestJS REST API powering the Proposly proposal management platform.
- Framework: NestJS v11 (TypeScript)
- Database: PostgreSQL via TypeORM
- Auth: JWT + Passport (local + Google OAuth)
- AI: Anthropic Claude SDK (
claude-opus-4-5for generation,claude-haiku-4-5for edits) - Payments: Stripe (international) + Paystack (Nigeria)
- Email: @nestjs-modules/mailer with Handlebars templates
- PDF: Puppeteer + marked (markdown → HTML → PDF)
- Rate limiting: @nestjs/throttler
- Node.js 20+
- PostgreSQL running locally
npm installcp .env.example .envFill in all values — see Environment variables below.
# Development (watch mode)
npm run start:dev
# Production
npm run build && npm run start:prodServer starts on http://localhost:3001. All routes are prefixed with /api.
| Variable | Description |
|---|---|
PORT |
Server port (default 3001) |
NODE_ENV |
development or production |
DB_HOST |
PostgreSQL host |
DB_PORT |
PostgreSQL port (default 5432) |
DB_USERNAME |
Database user |
DB_PASSWORD |
Database password |
DB_NAME |
Database name |
JWT_SECRET |
Secret used to sign JWT tokens |
JWT_EXPIRY |
Token expiry e.g. 7d |
ANTHROPIC_API_KEY |
Anthropic API key for Claude |
FREE_AI_GENERATIONS_LIMIT |
Max AI generations for free users (default 3) |
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
GOOGLE_CALLBACK_URL |
OAuth callback — e.g. http://localhost:3001/api/auth/google/callback |
STRIPE_SECRET_KEY |
Stripe secret key |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret |
STRIPE_PRO_PRICE_ID |
Stripe Price ID for the Pro plan |
PAYSTACK_SECRET_KEY |
Paystack secret key (also used for webhook HMAC verification) |
PAYSTACK_BASE_URL |
Paystack API base (default https://api.paystack.co) |
PAYSTACK_PRO_PLAN_CODE |
Paystack Plan code for the Pro subscription |
MAIL_HOST |
SMTP host |
MAIL_PORT |
SMTP port |
MAIL_USER |
SMTP username |
MAIL_PASSWORD |
SMTP password |
MAIL_FROM |
Sender address |
MAIL_FROM_NAME |
Sender display name |
FRONTEND_URL |
Frontend origin — used in email links and OAuth redirects |
synchronize: trueis enabled in development so TypeORM applies schema changes automatically. Never use this in production.
All routes require Authorization: Bearer <token> unless marked Public.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /register |
Public | Register with email + password |
| POST | /login |
Public | Login, returns JWT + user |
| GET | /verify-email?token= |
Public | Verify email address |
| POST | /forgot-password |
Public | Send password reset link |
| POST | /reset-password |
Public | Reset password with token |
| POST | /resend-verification |
Public | Resend verification email |
| GET | /google |
Public | Initiate Google OAuth |
| GET | /google/callback |
Public | Google OAuth callback |
| GET | /me |
JWT | Get current user |
| Method | Path | Description |
|---|---|---|
| PATCH | /me |
Update profile (name, profession, country) |
| GET | /me/notifications |
Get notification preferences |
| PATCH | /me/notifications |
Update notification preferences |
| Method | Path | Description |
|---|---|---|
| POST | / |
Create proposal |
| GET | / |
List proposals — filterable by clientId, status |
| GET | /:id |
Get single proposal |
| PATCH | /:id |
Update proposal |
| DELETE | /:id |
Delete proposal |
| POST | /:id/generate |
AI-generate content (free tier limited) |
| POST | /:id/send |
Email proposal to client + mark as sent |
| POST | /:id/polish |
AI-polish full document — Pro only |
| GET | /:id/export/pdf |
Download as PDF |
| Method | Path | Description |
|---|---|---|
| POST | / |
Create client |
| GET | / |
List clients |
| GET | /:id |
Get client |
| PATCH | /:id |
Update client |
| DELETE | /:id |
Delete client |
| Method | Path | Description |
|---|---|---|
| POST | /rewrite |
Rewrite selected text with instruction — Pro only |
Instructions: rewrite shorten formal fix
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /stripe/checkout |
JWT | Create Stripe checkout session |
| POST | /stripe/portal |
JWT | Open Stripe customer portal |
| POST | /stripe/webhook |
Public | Stripe webhook (raw body + signature) |
| POST | /paystack/initialize |
JWT | Initialize Paystack transaction |
| POST | /paystack/webhook |
Public | Paystack webhook (HMAC-SHA512) |
| Feature | Free | Pro |
|---|---|---|
| AI proposal generation | Up to FREE_AI_GENERATIONS_LIMIT |
Unlimited |
| AI Polish | — | ✓ |
| Inline AI editing | — | ✓ |
| PDF export | ✓ | ✓ |
| Send to client | ✓ | ✓ |
src/
├── modules/
│ ├── auth/ # JWT, Google OAuth, password reset, email verification
│ ├── users/ # Profile update, notification preferences
│ ├── clients/ # Client CRUD
│ ├── proposals/ # Proposal CRUD, AI generation, send email, polish, PDF export
│ ├── ai/ # Anthropic Claude wrapper
│ ├── billing/ # Stripe + Paystack checkout and webhooks
│ └── mail/ # Handlebars email templates
├── common/
│ ├── filters/ # Global HTTP exception filter
│ ├── guards/ # JwtAuthGuard, GoogleAuthGuard
│ ├── decorators/ # @CurrentUser()
│ └── utils/ # successResponse / errorResponse helpers
└── config/ # Typed environment loader
Stripe:
stripe listen --forward-to localhost:3001/api/billing/webhookPaystack: Use Paystack's test dashboard to trigger test events, or send a manual POST to /api/billing/paystack/webhook with a valid HMAC signature.