Full-stack test project with:
frontend— React + TypeScript + Redux Toolkit + React Hook Formbackend— NestJS + TypeORM + PostgreSQL + JWT auth (access + refresh cookie)
- Responsive landing page with deals grid (data from backend)
- Login / Register pages with field validation and backend error mapping to inputs
- Forgot password modal (UI stub)
- JWT auth flow:
- access token in frontend state/localStorage
- refresh token in
httpOnlycookie - auto logout on
401
- Shared styling system with global CSS variables
- Node.js 18+
- npm
- PostgreSQL database
- Go to backend:
cd backend
npm install- Create
.envinbackend/and configure:
PORT=3001
DB_SSL=true
DB_SYNCHRONIZE=true
JWT_SECRET=change-me-secret
JWT_EXPIRES_IN=1d
JWT_REFRESH_SECRET=change-me-refresh-secret
JWT_REFRESH_EXPIRES_IN=7d
FRONTEND_URL=http://localhost:5173
COOKIE_SECURE=false
POSTGRES_HOST=aws-1-eu-west-1.pooler.supabase.com
POSTGRES_PORT=6543
POSTGRES_USER=postgres.evgcaevbkzlhbngfirpa
POSTGRES_PASSWORD=r&P54h6bi_W%Lb.
POSTGRES_DB=postgres
AWS_REGION=
AWS_S3_BUCKET=
AWS_S3_PUBLIC_BASE_URL=
AWS_S3_ENDPOINT=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=- Start backend:
npm run start:devBackend runs on http://localhost:3001 by default.
- Go to frontend:
cd frontend
npm install- Create
.envinfrontend/:
VITE_API_BASE_URL=http://localhost:3001- Start frontend:
npm run devFrontend runs on http://localhost:5173.
POST /auth/registerPOST /auth/loginPOST /auth/refreshPOST /auth/logoutGET /auth/me(JWT protected)
GET /dealsGET /deals/getGET /deals/get/:id
-
POST /auth/register
- Body:
{ email, password, ... }(CreateUserDto) - Response:
{ accessToken, user }+ sets refreshToken cookie
- Body:
-
POST /auth/login
- Body:
{ email, password }(LoginDto) - Response:
{ accessToken, user }+ sets refreshToken cookie
- Body:
-
POST /auth/refresh
- Cookies:
refreshToken - Response:
{ accessToken }+ updates refreshToken cookie - 401 if refreshToken is missing
- Cookies:
-
POST /auth/logout
- Clears refreshToken cookie
- Response:
{ success: true }
-
GET /auth/me
- JWT authorization required
- Response:
{ userId, email }
-
POST /users
- Body:
{ email, password, ... }(CreateUserDto) - Response:
{ id, createdAt }
- Body:
-
GET /users
- Response: array of users
-
POST /applications
- Body: Application DTO + file (image, optional)
- Response: created application
-
GET /applications
- Query:
limit,offset(optional) - Response: array of applications
- Query:
-
GET /applications/currencies
- Response: array of currencies
-
GET /applications/:id
- Param:
id - Response: application
- Param:
-
PATCH /applications/:id
- Param:
id - Body: Application DTO + file (image, optional)
- Response: updated application
- Param:
-
DELETE /applications/:id
- Param:
id - Response: delete result
- Param:
-
GET /applications/:id/investments
- Param:
id - Response: array of investments
- Param:
-
POST /applications/:id/investments
- Param:
id - Body: Investment DTO
- Response: created investment
- Param:
-
PATCH /applications/:applicationId/investments/:investmentId
- Param:
applicationId,investmentId - Body: Investment DTO
- Response: updated investment
- Param:
npm run devnpm run buildnpm run preview
npm run start:devnpm run buildnpm run start:prodnpm test
- Email validation is strict and synchronized on both frontend and backend.
- Refresh cookie is cleared on logout.
- CORS allows
FRONTEND_URLandhttp://localhost:3000.