Internship Assignment Submission
A production-ready freelance marketplace with clean backend architecture, JWT auth system, role-based access control, and a responsive React frontend.
- Project Overview
- Tech Stack
- Folder Structure
- Database Schema
- API Reference
- Setup & Installation
- Environment Variables
- Design Decisions
TaskForge is a marketplace where:
- Clients post projects and manage bids
- Freelancers browse projects and submit competitive proposals
- Admins oversee and moderate the entire platform
The complete user journey:
Client posts project β Freelancer bids β Client accepts bid β
Freelancer delivers β Client confirms β Both leave reviews
| Layer | Technology |
|---|---|
| Runtime | Node.js v18+ |
| Framework | Express.js |
| Database | PostgreSQL (via Prisma ORM) |
| Auth | JWT (Access + Refresh Tokens) |
| Password | bcryptjs (salt rounds = 10) |
| Validation | Zod (schema-first validation) |
| Nodemailer (SMTP) | |
| Security | Helmet, CORS, express-rate-limit |
| Frontend | React (Vite), Vanilla CSS |
| HTTP Client | Axios (with token refresh interceptor) |
Freelance Project Marketplace/
βββ backend/
β βββ prisma/
β β βββ schema.prisma # Full DB schema (7 tables)
β β βββ seed.js # Admin user seeder
β βββ src/
β β βββ config/ # DB, JWT, Email configuration
β β βββ controllers/ # HTTP request handlers (thin layer)
β β βββ services/ # Business logic (ownership, rules)
β β βββ repositories/ # Data access (Prisma queries)
β β βββ routes/ # Express route definitions
β β βββ middleware/ # Auth, authorize, validate, errorHandler
β β βββ validators/ # Zod schemas for all request bodies
β β βββ utils/ # ApiError, ApiResponse, jwt, password, email
β βββ server.js # HTTP server entry point
β βββ .env.example # Environment variable template
β βββ package.json
βββ frontend/
βββ src/
β βββ context/ # AuthContext (global auth state)
β βββ services/ # API client (Axios)
β βββ components/ # Navbar, ProjectCard, ProtectedRoute
β βββ pages/ # All page components
β βββ index.css # Complete CSS design system
βββ .env # VITE_API_URL
βββ package.json
users
βββ projects (clientId β users.id)
βββ bids (freelancerId β users.id)
βββ reviews_given (reviewerId β users.id)
βββ reviews_received (targetId β users.id)
projects
βββ bids (projectId β projects.id)
βββ winning_bid (winningBidId β bids.id)
βββ reviews (projectId β projects.id)
bids
βββ UNIQUE(projectId, freelancerId) β one bid per freelancer per project
reviews
βββ UNIQUE(projectId, reviewerId) β one review per reviewer per project
| Table | Purpose |
|---|---|
users |
All platform users (CLIENT, FREELANCER, ADMIN) |
projects |
Project listings created by clients |
bids |
Freelancer bids on projects |
reviews |
Post-completion reviews |
refresh_tokens |
Persisted refresh tokens for JWT rotation |
verification_tokens |
Email verification & password reset tokens |
All endpoints prefixed with /api/v1.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /auth/register |
Register (CLIENT/FREELANCER) | Public |
| POST | /auth/login |
Login, get JWT tokens | Public |
| POST | /auth/logout |
Invalidate refresh token | Public |
| POST | /auth/refresh-token |
Get new access token | Public |
| POST | /auth/verify-email |
Verify email address | Public |
| POST | /auth/forgot-password |
Send reset email | Public |
| POST | /auth/reset-password |
Reset with token | Public |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /projects |
Create project | CLIENT |
| GET | /projects |
List (paginate/filter) | Public |
| GET | /projects/:id |
Get single project | Public |
| PUT | /projects/:id |
Update (owner only) | CLIENT |
| DELETE | /projects/:id |
Delete (owner only) | CLIENT |
| POST | /projects/:id/deliver |
Mark delivered | FREELANCER |
| POST | /projects/:id/complete |
Mark completed | CLIENT |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /projects/:id/bids |
Place bid | FREELANCER |
| GET | /projects/:id/bids |
View bids (owner only) | CLIENT |
| PUT | /bids/:id |
Update bid (owner only) | FREELANCER |
| DELETE | /bids/:id |
Withdraw bid | FREELANCER |
| POST | /bids/:id/accept |
Accept bid | CLIENT |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /reviews |
Submit review | CLIENT/FREELANCER |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /admin/users |
List all users | ADMIN |
| PATCH | /admin/users/:id/ban |
Ban user | ADMIN |
| PATCH | /admin/users/:id/unban |
Unban user | ADMIN |
| GET | /admin/projects |
List all projects | ADMIN |
- Node.js v18+
- PostgreSQL (local or cloud e.g. Supabase/Neon)
- A Mailtrap/SMTP account for emails
cd "Freelance Project Marketplace"cd backend
# Install dependencies
npm install
# Copy and fill in environment variables
copy .env.example .env
# Edit .env with your PostgreSQL URL, JWT secrets, SMTP credentials
# Run Prisma migrations (creates all tables)
npx prisma migrate dev --name init
# Generate Prisma client
npx prisma generate
# Seed the admin user
npx prisma db seed
# Start the backend
npm run devcd ../frontend
# Install dependencies
npm install
# Start the frontend
npm run dev- Frontend: http://localhost:5173
- Backend API: http://localhost:5000
- Health Check: http://localhost:5000/health
# Server
NODE_ENV=development
PORT=5000
# Database
DATABASE_URL="postgresql://USER:PASS@HOST:5432/taskforge_db"
# JWT (generate strong random strings)
JWT_ACCESS_SECRET=your_64_char_random_string
JWT_REFRESH_SECRET=another_64_char_random_string
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
# Frontend URL (for CORS + email links)
CLIENT_URL=http://localhost:5173
# SMTP (use Mailtrap for dev)
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=587
SMTP_USER=your_mailtrap_username
SMTP_PASS=your_mailtrap_password
EMAIL_FROM="TaskForge <noreply@taskforge.com>"
# Admin Seed
ADMIN_EMAIL=admin@taskforge.com
ADMIN_PASSWORD=Admin@TaskForge123
β οΈ Security: Generate JWT secrets withnode -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Each layer has a single responsibility:
- Repository β only knows about the database
- Service β only knows about business rules
- Controller β only knows about HTTP
- Middleware β cross-cutting concerns (auth, validation, errors)
This means you can swap Prisma for raw SQL or Express for Fastify by changing only the relevant layer.
- Schema-first approach makes the API contract explicit and self-documenting
- Zod's
parse()strips unknown fields (prevents parameter pollution) - Errors are automatically formatted into field-level messages by the global handler
- Type-safe queries that catch errors at compile-time (in TypeScript) or are clear at runtime
- Migrations are version-controlled and reproducible
- The schema file is the single source of truth for the database structure
- Anti-enumeration: Login and forgot-password always return generic messages
- Token revocation: Refresh tokens are stored in DB; logout deletes them
- Password resets revoke all sessions (deleteAllRefreshTokensForUser)
- Banning checks happen at login AND on every refresh token use
- Rate limiting on auth routes (10 req/15min) and globally (200 req/15min)
- Helmet sets 12 security headers including CSP, HSTS, X-Frame-Options
OPEN β IN_PROGRESS β DELIVERED β COMPLETED
Each transition is validated server-side before allowing the next action.