The GMU Bookswap application has been refactored to use a clean separation of concerns:
- Frontend: UI only, no direct database or Supabase access
- Backend (FastAPI): All authentication, authorization, validation, and database operations
This is a more maintainable, secure, and scalable architecture for production.
Frontend (Browser)
↓
├──→ Supabase Directly (for auth & queries)
│ - Required NEXT_PUBLIC_SUPABASE_URL
│ - Required NEXT_PUBLIC_SUPABASE_ANON_KEY
│ - Mixed business logic between frontend and backend
│
└──→ FastAPI Backend (for some book operations)
- Inconsistent approach
Problems:
- Frontend had direct access to Supabase
- Business logic scattered across frontend and backend
- Confusing which operations went through backend vs. direct
- Frontend needed Supabase credentials
Frontend (Browser)
↓
└──→ FastAPI Backend ONLY
↓
└──→ Supabase (database + auth)
Benefits:
- ✅ Single source of truth for business logic
- ✅ Centralized authentication and authorization
- ✅ Frontend is purely presentational
- ✅ Easier to test and maintain
- ✅ Backend can be swapped without frontend changes
- ✅ No Supabase credentials needed in frontend
Complete authentication router with endpoints:
POST /auth/signup- Register new user with GMU emailPOST /auth/login- Authenticate and return JWT tokensPOST /auth/logout- Sign out userGET /auth/me- Get current user infoPOST /auth/refresh- Refresh access token
All endpoints include:
- Pydantic validation (GMU email pattern, password length, etc.)
- Email verification enforcement
- Comprehensive error handling
- JWT token management
from routers import books, auth
app.include_router(auth.router, prefix="/auth", tags=["Authentication"])
app.include_router(books.router, prefix="/books", tags=["Books"])Complete rewrite - removed all Supabase dependencies:
Token Management:
// Token storage in localStorage
export function getAccessToken(): string | null
export function setAccessToken(token: string): void
export function getRefreshToken(): string | null
export function setRefreshToken(token: string): void
export function clearTokens(): void
export function setTokens(accessToken: string, refreshToken: string): voidAuthentication Functions:
export async function signup(data: SignupData): Promise<{ message: string }>
export async function login(data: LoginData): Promise<AuthResponse>
export async function logout(): Promise<void>
export async function getCurrentUser(): Promise<UserData>
export async function refreshAccessToken(): Promise<string>
export function isAuthenticated(): booleanAPI Helpers:
export async function apiGet(endpoint: string): Promise<Response>
export async function apiPost(endpoint: string, body: any): Promise<Response>
export async function apiPut(endpoint: string, body: any): Promise<Response>
export async function apiDelete(endpoint: string): Promise<Response>All API calls automatically include JWT token from localStorage in Authorization header.
Simplified to only require API URL:
export const config = {
api: {
url: getEnvVar('NEXT_PUBLIC_API_URL'),
},
} as const;Removed Supabase URL and anon key requirements.
# Only one environment variable needed!
NEXT_PUBLIC_API_URL=http://localhost:8000Client-side route protection that checks authentication status:
const PROTECTED_ROUTES = ["/listing", "/sell", "/my-listings", "/cart"];
// Redirects to login if not authenticated<CartProvider>
<RouteGuard>
{children}
</RouteGuard>
</CartProvider>-
/frontend/app/page.tsx(Login)- Uses
login()from api.ts - Stores JWT tokens in localStorage
- No Supabase imports
- Uses
-
/frontend/app/signup/page.tsx- Uses
signup()from api.ts - Added full_name field
- No Supabase imports
- Uses
-
/frontend/app/components/top-navbar.tsx- Uses
logout()from api.ts - Clears local tokens on logout
- Uses
-
/frontend/app/cart/page.tsx- Uses
getCurrentUser()from api.ts - No Supabase imports
- Uses
-
/frontend/app/my-listings/page.tsx- Uses
getCurrentUser()from api.ts - No Supabase imports
- Uses
-
/frontend/app/sell/page.tsx- Uses
getCurrentUser()from api.ts - No Supabase imports
- Uses
/frontend/lib/supabaseClient.ts- No longer needed/frontend/middleware.ts- Replaced with RouteGuard component
# Before (3 variables required):
NEXT_PUBLIC_SUPABASE_URL=https://...
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
NEXT_PUBLIC_API_URL=http://localhost:8000
# After (1 variable required):
NEXT_PUBLIC_API_URL=http://localhost:8000SUPABASE_URL=https://...
SUPABASE_KEY=eyJ...User fills form → Frontend calls POST /auth/signup
→ Backend validates GMU email + password
→ Backend creates user in Supabase
→ Supabase sends verification email
→ User verifies email
→ User can login
User enters credentials → Frontend calls POST /auth/login
→ Backend validates with Supabase
→ Backend checks email_confirmed_at
→ Backend returns access_token + refresh_token
→ Frontend stores tokens in localStorage
→ Frontend redirects to /listing
User performs action → Frontend calls API with token
→ Backend validates JWT token
→ Backend checks authorization (ownership, etc.)
→ Backend performs database operation
→ Returns result to frontend
User clicks logout → Frontend calls POST /auth/logout
→ Backend invalidates session
→ Frontend clears localStorage tokens
→ Frontend redirects to /
- No Exposed Credentials: Frontend no longer has direct access to database
- Centralized Authorization: All ownership checks happen in backend
- Token-Based Auth: JWT tokens with automatic expiration
- Server-Side Validation: All Pydantic validation on backend
- Email Verification: Enforced at backend login endpoint
- Single Entry Point: All requests go through FastAPI for logging/monitoring
-
Update
.env.local(frontend):# Remove these: # NEXT_PUBLIC_SUPABASE_URL=... # NEXT_PUBLIC_SUPABASE_ANON_KEY=... # Keep only: NEXT_PUBLIC_API_URL=http://localhost:8000
-
Start Backend:
cd backend/supabase python -m uvicorn main:app --reload -
Start Frontend:
cd frontend npm run dev -
Test Authentication:
- Go to http://localhost:3000
- Create new account (will send verification email)
- Verify email via link
- Login with verified account
- All protected routes should work
-
Backend Deployment:
- Deploy FastAPI to your server/cloud
- Set environment variables for Supabase
- Ensure CORS includes your frontend domain
-
Frontend Deployment:
- Update
NEXT_PUBLIC_API_URLto production backend URL - Build:
npm run build - Deploy to Vercel/Netlify/etc.
- Update
-
DNS/SSL:
- Backend should be HTTPS in production
- Update CORS settings to match frontend domain
- User can signup with GMU email
- Verification email is sent
- Unverified users cannot login
- Verified users can login
- Login returns access token
- Token is stored in localStorage
- Protected routes check authentication
- Unauthenticated users are redirected to login
- Users can create listings (authenticated)
- Users can only delete their own listings
- Users can logout
- Logout clears tokens
- After logout, protected routes redirect to login
- Token Refresh: Implement automatic token refresh when access token expires
- Remember Me: Optional long-lived refresh tokens
- Password Reset: Add password reset flow via email
- Session Management: Track active sessions, force logout on all devices
- Rate Limiting: Add rate limits to auth endpoints
- 2FA: Two-factor authentication for added security
- Restart your Next.js dev server after adding/changing .env.local
- Environment variables are only loaded at startup
- Check if access token exists in localStorage
- Token might have expired - implement refresh logic
- Backend might not be running
- Ensure backend CORS middleware includes frontend URL
- Check that frontend is making requests to correct API URL
- Check spam folder for verification email
- Resend verification from Supabase dashboard if needed
This refactor creates a production-ready architecture where:
- Frontend is a pure UI layer communicating only with your backend
- Backend handles ALL business logic, auth, and database access
- Easier to maintain, test, and scale
- More secure with centralized validation and authorization
- Consistent API patterns throughout the application
The frontend is now completely agnostic of your database provider - you could switch from Supabase to PostgreSQL, MongoDB, etc. without changing any frontend code!