Connecting surplus food with those who need it most.
- Introduction
- About
- Features
- Architecture
- Getting Started
- Environment Variables
- User Roles & Permissions
- API Reference
- Swagger API Documentation
- Frontend Pages & Routes
- Shared Types & DTOs
- Docker Services
- Running Tests
- Contributing
- Troubleshooting
- License
SurplusSync is a full-stack web platform that connects food donors (restaurants, caterers, individuals) with NGOs and volunteers to reduce food waste and feed communities in need. Donors list surplus food, NGOs discover and claim donations via an interactive map, and volunteers coordinate pickups and deliveries - all in real time.
Example workflow:
Donor lists 50 plates of Biryani
↓
NGO discovers it on the map (2.3 km away)
↓
NGO claims the donation
↓
Volunteer picks it up from the donor
↓
Volunteer delivers it to the NGO
↓
Everyone's impact dashboard updates
SurplusSync is designed to tackle the problem of food waste by creating a seamless bridge between those with excess food and organizations that can distribute it. The platform provides:
- Real-time geospatial discovery - NGOs find nearby food on an interactive Leaflet map
- Role-based workflows - Each user type (Donor, NGO, Volunteer) gets a purpose-built dashboard
- Food safety tracking - Hygiene checklists, preparation timestamps, and expiry monitoring
- Impact analytics - Track meals provided, CO₂ saved, and kilograms redistributed
- Image uploads - Donors can attach up to 5 photos per donation via Cloudinary
- JWT authentication - Secure, token-based auth across all protected endpoints
- Create Food Donations - List surplus food with name, food type, quantity, unit, images, preparation time, expiry time, hygiene checklist, and special instructions
- Geolocated Listings - Attach latitude/longitude and address so NGOs can find your food on the map
- Image Uploads - Upload up to 5 images per donation (stored on Cloudinary)
- Track Donation Status - Monitor your donations through
AVAILABLE→CLAIMED→PICKED_UP→DELIVERED - Impact Dashboard - View your contribution metrics (total donations, meals provided, CO₂ saved, kg redistributed)
- Donation History - Browse all your past and present donations
- Discovery Map - Interactive Leaflet map with geolocation-based filtering by radius
- Claim Donations - Reserve available food with an estimated pickup time
- NGO Dashboard - Dedicated dashboard showing claimed donations, active pickups, and delivery status
- Mark as Delivered - Confirm receipt of food donations
- Impact Statistics - View aggregated impact across all claimed and delivered donations
- Volunteer Dashboard - Dedicated view with assigned tasks and status updates
- Pickup Confirmation - Mark food as picked up from the donor
- Delivery Confirmation - Mark food as delivered to the NGO
- Transport Coordination - Bridge communication between donors and NGOs
| Layer | Technology | Purpose |
|---|---|---|
| Backend Framework | NestJS v11 | Modular, TypeScript-first Node.js framework |
| Database | PostgreSQL 15 with PostGIS | Relational database with geospatial extensions |
| ORM | TypeORM | Entity-based database management with auto-sync |
| Authentication | Passport.js + JWT | Secure token-based authentication |
| File Storage | Cloudinary | Cloud-based image upload and transformation |
| API Documentation | Swagger/OpenAPI | Interactive API docs at /api |
| Validation | class-validator + class-transformer | DTO-based request validation |
| Password Hashing | bcrypt | Secure password storage |
| Frontend Framework | React 18 | Component-based UI with hooks |
| Build Tool | Vite | Fast HMR and optimized production builds |
| Styling | Tailwind CSS 3 | Utility-first CSS framework |
| State Management | LocalStorage + React State | Persistent auth/user data across refreshes with component-level state via useState |
| Maps | Leaflet + React-Leaflet | Interactive map-based food discovery |
| HTTP Client | Axios | Promise-based HTTP requests with interceptors |
| Icons | Lucide React | Modern, clean SVG icon library |
| Routing | React Router v6 | Client-side routing with nested layouts |
| Containerization | Docker + Docker Compose | Multi-service orchestration |
| Caching | Redis | In-memory key-value store (Alpine image) |
| DB Administration | pgAdmin 4 | Web-based PostgreSQL management |
Food-Redistribution-Platform/
├── backend/ # NestJS API server
│ ├── src/
│ │ ├── auth/ # Authentication module
│ │ │ ├── dto/ # RegisterDto, LoginDto, UpdateProfileDto
│ │ │ ├── entities/ # User entity (TypeORM)
│ │ │ ├── guards/ # JwtAuthGuard
│ │ │ ├── jwt.strategy.ts # Passport JWT strategy
│ │ │ ├── auth.controller.ts # Auth endpoints
│ │ │ ├── auth.module.ts # Module definition
│ │ │ ├── auth.service.ts # Business logic
│ │ │ └── auth.service.spec.ts
│ │ ├── donations/ # Donations module
│ │ │ ├── dto/ # CreateDonationDto, ClaimDonationDto, UpdateDonationStatusDto
│ │ │ ├── entities/ # Donation entity (TypeORM)
│ │ │ ├── donations.controller.ts
│ │ │ ├── donations.module.ts
│ │ │ ├── donations.service.ts
│ │ │ └── donations.service.spec.ts
│ │ ├── common/ # Shared services
│ │ │ └── cloudinary.service.ts
│ │ ├── app.module.ts # Root module
│ │ └── main.ts # Bootstrap & Swagger setup
│ ├── Dockerfile
│ └── package.json
│
├── frontend/ # React + Vite SPA
│ ├── src/
│ │ ├── pages/
│ │ │ ├── LandingPage.tsx # Public landing page
│ │ │ ├── Login.tsx # Login form
│ │ │ ├── Register.tsx # Registration form (role selection)
│ │ │ └── dashboard/
│ │ │ ├── DonorHome.tsx # Donor dashboard
│ │ │ ├── NGODashboard.tsx # NGO dashboard
│ │ │ ├── VolunteerDashboard.tsx # Volunteer dashboard
│ │ │ ├── AddFood.tsx # Create donation form
│ │ │ ├── DiscoveryMap.tsx # Interactive Leaflet map
│ │ │ ├── History.tsx # Donation history
│ │ │ ├── Impact.tsx # Impact analytics
│ │ │ ├── Notifications.tsx # Notification center
│ │ │ └── Profile.tsx # User profile management
│ │ ├── layouts/
│ │ │ └── DashboardLayout.tsx # Sidebar navigation + role-based routing
│ │ ├── services/
│ │ │ └── api.ts # Axios instance, types, API calls
│ │ ├── App.tsx # Router configuration
│ │ ├── main.tsx # React entry point
│ │ └── index.css # Tailwind + custom styles
│ ├── Dockerfile
│ └── package.json
│
├── shared/ # Shared TypeScript types
│ ├── types/
│ │ ├── user.types.ts
│ │ ├── donations.types.ts
│ │ └── common.types.ts
│ └── dtos/
│ ├── auth.dto.ts
│ └── donation.dto.ts
│
├── docker-compose.yml # Multi-service orchestration
├── .env.example # Environment variable template
├── .gitignore
├── API_CONTRACT.md # Detailed API documentation
└── README.md # This file
Donations follow a strict lifecycle managed through role-based state transitions:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ AVAILABLE │───▶│ CLAIMED │────▶│ PICKED_UP │───▶│ DELIVERED │
│ │ │ │ │ │ │ │
│ Donor lists │ │ NGO claims │ │ Volunteer │ │ Volunteer │
│ surplus food│ │ the food │ │ picks up │ │ delivers │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Donor NGO Volunteer Volunteer
Rules:
- Only NGOs can claim a donation (
AVAILABLE→CLAIMED) - Only Volunteers can update status (
CLAIMED→PICKED_UP→DELIVERED) - Status transitions cannot skip steps (e.g.,
CLAIMED→DELIVEREDis invalid) - Timestamps are automatically recorded at each transition
| Requirement | Version | Notes |
|---|---|---|
| Node.js | 18+ (20 recommended) | Runtime for both backend and frontend |
| npm | 9+ | Package manager (comes with Node.js) |
| PostgreSQL | 14+ | Required only for manual setup |
| Docker & Docker Compose | Latest | Required only for Docker setup |
| Git | 2.x+ | For cloning the repository |
The Docker setup spins up all services - PostgreSQL with PostGIS, Redis, pgAdmin, the NestJS backend, and the React frontend - in a single command.
1. Clone the repository:
git clone https://github.com/your-username/Food-Redistribution-Platform.git
cd Food-Redistribution-Platform2. Create your environment file:
cp .env.example .envEdit .env and set your JWT_SECRET and Cloudinary credentials. You can leave the database defaults for local development.
3. Build and start all services:
docker-compose up --build4. Access the services:
| Service | URL | Description |
|---|---|---|
| Frontend | http://localhost:5173 | React application |
| Backend API | http://localhost:3000 | NestJS REST API |
| Swagger Docs | http://localhost:3000/api | Interactive API documentation |
| pgAdmin | http://localhost:5050 | Database admin panel |
5. Stop all services:
docker-compose downTo also remove persistent database volumes:
docker-compose down -v- Install PostgreSQL 14+ and ensure the service is running
- Create a database:
CREATE DATABASE surplus_db;Note: TypeORM with
synchronize: truewill auto-create all tables on first backend startup. No manual migrations are required in development.
cd backend
npm install
# Configure environment - create a .env file in the project root
# or set these environment variables:
# DATABASE_HOST=localhost
# DATABASE_PORT=5432
# POSTGRES_USER=postgres
# POSTGRES_PASSWORD=your_password
# POSTGRES_DB=surplus_db
# JWT_SECRET=your_jwt_secret
# CLOUDINARY_CLOUD_NAME=your_cloud_name
# CLOUDINARY_API_KEY=your_api_key
# CLOUDINARY_API_SECRET=your_api_secret
# Start the development server
npm run start:devThe backend will start at http://localhost:3000 Swagger docs available at http://localhost:3000/api
cd frontend
npm install
# The frontend reads VITE_API_URL from the environment.
# Default is http://localhost:3000 (no .env needed for local dev)
# Start the development server
npm run devThe frontend will start at http://localhost:5173
- Open http://localhost:5173 - you should see the SurplusSync landing page
- Open http://localhost:3000/api - you should see the Swagger UI
- Register a new account via the UI or Swagger
- Log in and access your role-specific dashboard
Copy .env.example to .env in the project root. This file is used by docker-compose.yml and the backend:
# DATABASE CONFIGURATION
POSTGRES_USER=surplus_admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=surplus_db
POSTGRES_PORT=5432
# REDIS CONFIGURATION
REDIS_PORT=6379
# PGADMIN (DB DASHBOARD)
PGADMIN_EMAIL=admin@surplussync.com
PGADMIN_PASSWORD=adminsurp123
# JWT
JWT_SECRET=your_jwt_secret_here
# CLOUDINARY (Image Uploads)
CLOUDINARY_CLOUD_NAME=your_cloud_name_here
CLOUDINARY_API_KEY=your_api_key_here
CLOUDINARY_API_SECRET=your_api_secret_hereWhen running without Docker, the backend reads these variables (defaults shown):
| Variable | Default | Description |
|---|---|---|
DATABASE_HOST |
postgres |
PostgreSQL host (localhost for manual setup) |
DATABASE_PORT |
5432 |
PostgreSQL port |
POSTGRES_USER |
student |
Database username |
POSTGRES_PASSWORD |
student |
Database password |
POSTGRES_DB |
surplus_db |
Database name |
JWT_SECRET |
- | Required. Secret key for JWT token signing |
CLOUDINARY_CLOUD_NAME |
- | Cloudinary account cloud name |
CLOUDINARY_API_KEY |
- | Cloudinary API key |
CLOUDINARY_API_SECRET |
- | Cloudinary API secret |
| Variable | Default | Description |
|---|---|---|
VITE_API_URL |
http://localhost:3000 |
Backend API base URL |
SurplusSync uses role-based access control. Users select their role during registration.
| Role | Dashboard | Key Permissions |
|---|---|---|
| DONOR | Donor Home | Create donations, upload images, view own listings, track status, view impact stats |
| NGO | NGO Dashboard | Browse available donations on map, claim food, mark as delivered, view impact stats |
| VOLUNTEER | Volunteer Dashboard | View claimed donations, confirm pickup, confirm delivery |
| ADMIN | - | Administrative role (reserved) |
Dashboard routing is automatic - after login, users are redirected to their role-specific dashboard:
- Donors →
/dashboard - NGOs →
/dashboard/ngo - Volunteers →
/dashboard/volunteer
All endpoints use JSON. Protected endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <your-jwt-token>Request Body:
{
"email": "donor@restaurant.com",
"password": "securepass123",
"name": "Annapurna Restaurant",
"role": "DONOR",
"phone": "+919876543210",
"organizationName": "Annapurna Foods",
"organizationType": "Restaurant",
"address": "Beach Road, Visakhapatnam"
}Success Response (201):
{
"success": true,
"data": {
"token": "eyJhbGci...",
"user": {
"id": "550e8400-...",
"email": "donor@restaurant.com",
"name": "Annapurna Restaurant",
"role": "DONOR"
}
},
"message": "User registered successfully"
}Request Body:
{
"email": "donor@restaurant.com",
"password": "securepass123"
}Success Response (200):
{
"success": true,
"data": {
"token": "eyJhbGci...",
"user": { "id": "...", "email": "...", "name": "...", "role": "DONOR" }
},
"message": "Login successful"
}Requires authentication
Returns the full user profile including organization details and capacity settings.
Requires authentication
Update name, phone, address, organization details, location coordinates, and capacity settings.
Requires authentication Supports multipart/form-data (for image uploads)
Form Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Name of the food item |
foodType |
string | Yes | Type: cooked, raw, packaged, fruits, bakery, dairy |
quantity |
number | Yes | Amount of food |
unit |
string | Yes | Unit: kg, plates, servings, packets, etc. |
preparationTime |
ISO 8601 | Yes | When the food was prepared |
expiryTime |
ISO 8601 | No | When the food expires |
latitude |
number | Yes | Pickup location latitude |
longitude |
number | Yes | Pickup location longitude |
description |
string | No | Additional food description |
specialInstructions |
string | No | e.g., "Keep refrigerated. Contains nuts." |
images |
File[] | No | Up to 5 image files (uploaded to Cloudinary) |
Success Response (201):
{
"id": "660e8400-...",
"name": "Vegetable Biryani",
"foodType": "cooked",
"quantity": 50,
"unit": "servings",
"status": "AVAILABLE",
"latitude": 17.6868,
"longitude": 83.2185,
"imageUrls": ["https://res.cloudinary.com/..."],
"createdAt": "2025-01-12T10:30:00Z"
}Query Parameters (optional):
| Parameter | Type | Default | Description |
|---|---|---|---|
latitude |
number | - | Searcher's latitude for distance filter |
longitude |
number | - | Searcher's longitude for distance filter |
radius |
number | 5 |
Search radius in kilometers |
Example:
GET /donations?latitude=17.6868&longitude=83.2185&radius=10Returns an array of donation objects sorted by creation time.
Requires authentication
Request Body:
{
"estimatedPickupTime": "2025-01-12T15:00:00Z"
}Changes donation status from AVAILABLE → CLAIMED and records the claiming NGO.
Requires authentication
Request Body:
{
"status": "PICKED_UP"
}Valid values: PICKED_UP, DELIVERED
Status transitions must follow the correct order: CLAIMED → PICKED_UP → DELIVERED
Requires authentication
Shortcut endpoint to mark a claimed donation as DELIVERED.
All API errors follow a consistent structure:
{
"success": false,
"message": "Human-readable error description",
"errors": ["Detailed error 1", "Detailed error 2"],
"statusCode": 400
}| Code | Meaning | When Used |
|---|---|---|
200 |
OK | Successful GET, PATCH |
201 |
Created | Successful POST (resource created) |
400 |
Bad Request | Validation error, invalid status transition |
401 |
Unauthorized | Missing or invalid JWT token |
403 |
Forbidden | Valid token but insufficient role permissions |
404 |
Not Found | Donation or resource doesn't exist |
500 |
Internal Server Error | Unhandled server-side error |
The backend automatically generates interactive API documentation using Swagger/OpenAPI.
How to use:
- Start the backend server
- Navigate to http://localhost:3000/api
- Click on any endpoint → "Try it out" → fill in the request body → "Execute"
- For protected endpoints, click "Authorize" at the top, enter
Bearer <your-token>, and click "Authorize"
| Route | Component | Access | Description |
|---|---|---|---|
/ |
LandingPage |
Public | Marketing landing page with hero section |
/login |
Login |
Public | Email + password login form |
/register |
Register |
Public | Registration with role selection (Donor/NGO/Volunteer) |
/dashboard |
DonorHome |
Donor | Donor's main dashboard with active donations |
/dashboard/ngo |
NGODashboard |
NGO | NGO-specific dashboard with claimed donations |
/dashboard/volunteer |
VolunteerDashboard |
Volunteer | Volunteer task list with pickup/delivery actions |
/dashboard/add |
AddFood |
Donor | Multi-step donation creation form with image upload |
/dashboard/map |
DiscoveryMap |
All | Interactive Leaflet map showing nearby available food |
/dashboard/history |
History |
All | Donation history log |
/dashboard/impact |
Impact |
All | Impact analytics (meals, CO₂, kg redistributed) |
/dashboard/notifications |
Notifications |
All | Notification center |
/dashboard/profile |
Profile |
All | User profile management |
The DashboardLayout component provides a persistent sidebar with role-filtered navigation links, user info display, and notification badges.
The shared/ directory contains TypeScript interfaces and DTOs used by both the frontend and backend to ensure type consistency across the stack:
shared/
├── types/
│ ├── user.types.ts # User, UserRole interfaces
│ ├── donations.types.ts # FoodListing, DonationStatus, Location
│ └── common.types.ts # ApiResponse, ApiError generics
└── dtos/
├── auth.dto.ts # LoginDto, RegisterDto, AuthResponse
└── donation.dto.ts # CreateDonationDto
Usage in frontend:
import { User, UserRole } from '../../../shared/types/user.types';
import { FoodListing } from '../../../shared/types/donation.types';
import { ApiResponse } from '../../../shared/types/common.types';The docker-compose.yml orchestrates five services:
| Service | Image | Container | Port | Description |
|---|---|---|---|---|
| postgres | postgis/postgis:15-3.3 |
surplus_db |
5432 |
PostgreSQL with PostGIS extension |
| redis | redis:alpine |
surplus_redis |
6379 |
Redis with AOF persistence |
| pgadmin | dpage/pgadmin4 |
surplus_pgadmin |
5050 |
Web-based database management |
| backend | node:20-alpine (custom) |
surplus_backend |
3000 |
NestJS API with hot reload |
| frontend | node:20-alpine (custom) |
surplus_frontend |
5173 |
Vite dev server with HMR |
Features:
- Hot reloading - Both backend and frontend volumes are mounted for live code changes
- Persistent data - Database data persists in a named
postgres_datavolume - Networking - Backend connects to
postgresandredisby container name - Shared types - The
shared/directory is mounted into the backend container
cd backend
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:cov
# Run end-to-end tests
npm run test:e2e# Backend
cd backend
npm run lint # ESLint with auto-fix
npm run format # Prettier formatting
# Frontend
cd frontend
npm run lint # ESLint# Backend
cd backend
npm run build # Compiles to dist/
npm run start:prod # Runs compiled code
# Frontend
cd frontend
npm run build # Vite production build
npm run preview # Preview production build locallyWe welcome contributions! Please follow these steps:
- Fork the repository
- Clone your fork:
git clone https://github.com/your-username/Food-Redistribution-Platform.git
- Create a feature branch:
git checkout -b feature/amazing-feature
- Install dependencies:
cd backend && npm install cd ../frontend && npm install
- Make your changes and ensure tests pass:
cd backend && npm test
- Commit your changes:
git commit -m "feat: add amazing feature" - Push to your fork:
git push origin feature/amazing-feature
- Open a Pull Request against the
mainbranch
We follow Conventional Commits:
| Prefix | Use |
|---|---|
feat: |
New feature |
fix: |
Bug fix |
docs: |
Documentation only |
style: |
Formatting, no code change |
refactor: |
Code restructuring |
test: |
Adding or updating tests |
chore: |
Build process, tooling |
Port conflicts:
If port 5432, 3000, or 5173 is already in use, change the port mapping in .env or docker-compose.yml.
Database connection refused:
# Check if postgres container is running
docker ps
# View postgres logs
docker logs surplus_db
# Restart all services
docker-compose down && docker-compose up --buildClean rebuild:
docker-compose down -v
docker system prune -f
docker-compose up --buildMODULE_NOT_FOUND error:
cd backend
rm -rf node_modules
npm installDatabase sync errors:
TypeORM synchronize: true is enabled in development. If your entity schema is out of sync, try dropping and re-creating the database:
DROP DATABASE surplus_db;
CREATE DATABASE surplus_db;Then restart the backend.
Vite not connecting to backend:
Ensure VITE_API_URL is set and CORS is configured. The backend enables CORS for http://localhost:5173 by default.
Map tiles not loading:
The Discovery Map uses OpenStreetMap tiles. Ensure you have an active internet connection.
This project is licensed under the MIT License. See the LICENSE file for details.
Built with ❤️ to reduce food waste and feed communities