Malaysia's Mineral and Spring Water Source Registry
CariAir is a Next.js 16 web platform serving as Malaysia's comprehensive mineral and spring water source registry. Users can search, compare, and learn about mineral water brands with detailed data on:
- pH levels
- Total Dissolved Solids (TDS)
- Mineral composition (calcium, magnesium, potassium, sodium, etc.)
- Water source locations with KKM approval numbers
- Brand and manufacturer information
- Interactive map of water sources
- Framework: Next.js 16 with App Router (React Server Components)
- Frontend: React 19, TypeScript (Strict Mode), Tailwind CSS
- UI Components: shadcn/ui (Radix UI primitives)
- Database: PostgreSQL with Drizzle ORM
- Authentication: NextAuth.js v5 (Auth.js) with Google OAuth + credentials
- Internationalization: next-intl (Malay
msas default, Englishen) - API Documentation: Swagger/OpenAPI at
/docs - Deployment: Docker + Docker Compose
- Node.js 20+
- pnpm 10+
- Docker (for PostgreSQL database)
# Clone the repository
git clone https://github.com/muazhazali/cariair.git
cd cariair
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Edit .env.local with your database credentialsRecommended: Database in Docker, Next.js locally (fastest)
# Terminal 1: Start PostgreSQL in Docker
pnpm run dev:db
# Terminal 2: Start Next.js dev server
pnpm devThe app will be available at http://localhost:3000
Alternative: Manual PostgreSQL setup
If you prefer not to use Docker:
# Setup PostgreSQL manually
createdb cariair
psql -d cariair -f sql/schema.sql
# Start dev server
pnpm devCreate .env.local for development:
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=your-password
DB_NAME=cariair
# Authentication (Required)
AUTH_SECRET=your-secret-key
# Google OAuth (Optional)
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret# Start with Docker Compose
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -dFor LXC containers or servers requiring auto-start:
# Install systemd service (Docker)
sudo ./scripts/install-systemd.sh
# Or native systemd (if Docker has issues)
sudo ./scripts/install-native.shSee PRODUCTION.md for detailed deployment instructions.
cariair/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ │ ├── export/ # CSV/JSON export endpoints
│ │ ├── openapi/ # OpenAPI specification
│ │ └── ...
│ ├── docs/ # API documentation (Swagger UI)
│ ├── (routes)/ # Page routes
│ └── layout.tsx # Root layout
├── components/ # React components
│ └── ui/ # shadcn/ui components
├── lib/ # Utilities and helpers
│ ├── db/ # Database operations
│ │ ├── schema.ts # Drizzle ORM schema
│ │ ├── drizzle.ts # Drizzle client
│ │ ├── products.ts # Product queries
│ │ ├── sources.ts # Source queries
│ │ └── ...
│ ├── auth.ts # NextAuth configuration
│ └── types/ # TypeScript types
├── drizzle/ # Drizzle ORM migrations
│ └── migrations/ # Migration files
├── sql/ # Database scripts
│ └── schema.sql # Full schema + seed data
├── i18n/ # Internationalization
├── messages/ # Translation files
│ ├── ms.json # Malay
│ └── en.json # English
└── scripts/ # Deployment scripts
- PostgreSQL with Drizzle ORM for type-safe queries
- NextAuth adapter tables for authentication
- Images stored as BYTEA in database
- Proper foreign key relationships between products, brands, sources, and manufacturers
- Connection pooling to prevent exhaustion
- Incremental database migrations with Drizzle Kit
/api/products- Product search and filtering with pagination/api/sources- Water source data with KKM approval numbers/api/brands- Brand listings and parent companies/api/manufacturers- Manufacturer information/api/images/[id]- Image retrieval/api/export/products- CSV export of all products/api/export/products/json- JSON export of all products/api/openapi- OpenAPI specification
Interactive API documentation is available at /docs powered by Swagger UI. The documentation includes:
- All available endpoints
- Request/response schemas
- Query parameters
- Example requests
Export all product data for analysis:
- CSV Export:
/api/export/products- Download ascariair-products.csv - JSON Export:
/api/export/products/json- Download ascariair-products.json
- Locale detection via cookie (not URL prefix)
- Malay (
ms) as default language - English (
en) as secondary - Easy to add more languages
| Command | Description |
|---|---|
pnpm dev |
Start development server |
pnpm build |
Build for production |
pnpm start |
Start production server |
pnpm lint |
Run ESLint |
pnpm run db:schema |
Initialize database schema |
pnpm run db:generate |
Generate Drizzle migrations |
pnpm run db:migrate |
Run database migrations |
pnpm run db:studio |
Open Drizzle Studio GUI |
The docker-compose.yml only includes PostgreSQL (not the full app). Next.js runs locally for faster development.
# Start PostgreSQL database
pnpm run dev:db
# View database logs
docker compose logs -f
# Stop PostgreSQL
pnpm run dev:db:stop
# Stop and remove volumes (resets database)
docker compose down -vSee PRODUCTION.md for full-stack Docker deployment.
We use Drizzle ORM for type-safe database operations and migrations:
# Generate migrations after schema changes
pnpm run db:generate
# Apply migrations to database
pnpm run db:migrate
# Open Drizzle Studio to browse data
pnpm run db:studioPlease read CONTRIBUTING.md for contribution guidelines.
- Change default database passwords in production
- Generate secure
AUTH_SECRETwithopenssl rand -base64 32 - Keep
.envfiles secure (chmod 600) - PostgreSQL port not exposed in production Docker setup
- TypeScript strict mode enabled for compile-time safety
See SECURITY.md for security policies.
This project is licensed under the MIT License - see LICENSE file for details.
See CHANGELOG.md for version history.
For issues or feature requests, please open a GitHub issue.
Made with ❤️ for Malaysia's water community