An AI-powered local guide Flutter app that helps you discover nearby attractions, restaurants, and activities through natural language queries.
- π€ AI-Powered Recommendations: Ask questions in natural language and get personalized suggestions
- π Location-Based Search: Uses your current location
- π·οΈ Categorized Results: Places organized by categories (Restaurant, Park, Museum, Activity, Landmark, Shopping)
- π Distance Display: Shows walking distance to each suggested location
- π Direct Links: Visit place websites directly from the app
- π¨ Modern UI: Clean Material 3 design with teal theme
- π Privacy-First: Anonymous authentication with device-based identification
- β‘ Rate Limited: Smart rate limiting per user to ensure fair usage
- π‘οΈ Secure: Backend-first architecture with no exposed API credentials
Left: Welcome screen with search interface and example queries | Right: AI-powered search results with nearby recommendations
BeMyGuide uses a secure, serverless architecture with anonymous authentication:
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Flutter App βββββΆβ Cloudflare βββββΆβ Workers AI β
β β’ Device ID β β Workers API β β (Llama 3.1-8B) β
β β’ JWT Token β β β’ Rate Limiting β β β
β β’ Location β β β’ Auth & AI β β β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Cloudflare KV β
β β’ User Sessions β
β β’ Rate Limits β
βββββββββββββββββββ
- Anonymous Auth: Zero-friction experience - no signup required
- Device Identity: Unique device ID generated on first launch
- JWT Tokens: Secure API authentication with automatic refresh
- Rate Limiting: 30 requests per 60 minutes per device (configurable)
- No Exposed Credentials: AI processing handled entirely in backend
- App generates unique device ID and gets JWT token from backend
- User queries sent to Cloudflare Workers API with location data
- Backend validates request, enforces rate limits, and processes with Workers AI
- AI responses processed and returned to app with proper formatting
Frontend: Flutter, Geolocator, HTTP client, JWT handling
Backend: Cloudflare Workers, Hono framework, Workers AI (Llama 3.1-8B)
Storage: Cloudflare KV for sessions and rate limiting
Infrastructure: Global edge deployment with zero cold starts
- Flutter SDK 3.24.0+
- Cloudflare account with Workers and AI access
- Node.js 18+ (for backend)
# Clone repository
git clone https://github.com/kmjayadeep/bemyguide.git
cd bemyguide
# Backend setup
cd backend
npm install
npm install -g wrangler
wrangler login
# Create KV namespaces
wrangler kv:namespace create "bmg-rate-dev"
wrangler kv:namespace create "bmg-rate"
# Update wrangler.jsonc with your namespace IDs in the env.dev and env.production sections
# Deploy backend
wrangler deploy
# Flutter setup
cd ../app
flutter pub get
# Update API endpoint in lib/services/api_service.dart
flutter runAll API requests require authentication using a JWT token. The process is:
- Generate a unique device ID on the client (e.g., UUID).
- Obtain a JWT by calling the anonymous auth endpoint:
curl -X POST http://localhost:8787/api/auth/anonymous \
-H "Content-Type: application/json" \
-d '{
"deviceId": "test-device-123"
}'Response:
{
"success": true,
"token": "<JWT_TOKEN>"
}- Use the JWT token in the Authorization header for all protected endpoints:
# Start local development server
cd backend
wrangler dev
# Get a JWT token
TOKEN=$(curl -s -X POST http://localhost:8787/api/auth/anonymous \
-H "Content-Type: application/json" \
-d '{"deviceId": "test-device-123"}' | jq -r .token)
# Test the API endpoint (replace $TOKEN with the value from above if not using jq)
curl -X POST http://localhost:8787/api/recommendations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"query": "Find nearby restaurants that serve vegan food",
"latitude": 40.7128,
"longitude": -74.0060
}'
# Test health check
curl http://localhost:8787/health# Get a JWT token with a consistent device ID
TOKEN=$(curl -s -X POST http://localhost:8787/api/auth/anonymous \
-H "Content-Type: application/json" \
-d '{"deviceId": "test-device-123"}' | jq -r .token)
# Make multiple requests in a loop to trigger rate limiting
for i in {1..35}; do
echo "Request $i:"
curl -s -X POST http://localhost:8787/api/recommendations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"query": "Find nearby restaurants",
"latitude": 40.7128,
"longitude": -74.0060
}' | jq '.success, .error, ."X-RateLimit-Remaining"'
echo ""
# Small delay between requests
sleep 1
doneAfter 30 requests, you should see a rate limit error with a 429 status code.
# Replace YOUR_WORKER_URL with your actual Cloudflare Workers URL
TOKEN=$(curl -s -X POST https://YOUR_WORKER_URL.workers.dev/api/auth/anonymous \
-H "Content-Type: application/json" \
-d '{"deviceId": "test-device-123"}' | jq -r .token)
curl -X POST https://YOUR_WORKER_URL.workers.dev/api/recommendations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"query": "Show me fun activities for families with kids",
"latitude": 48.8566,
"longitude": 2.3522
}'Expected Response:
{
"success": true,
"data": [
{
"name": "Local Cafe & Bistro",
"description": "Cozy local cafe with excellent coffee and pastries",
"category": "Restaurant",
"distance_km": 0.2,
"website_url": "https://example.com/cafe",
"latitude": null,
"longitude": null
},
{
"name": "Central Park",
"description": "Beautiful park perfect for walks and relaxation",
"category": "Park",
"distance_km": 0.5,
"website_url": null,
"latitude": null,
"longitude": null
},
{
"name": "Art Museum",
"description": "Contemporary art museum with rotating exhibitions",
"category": "Museum",
"distance_km": 1.2,
"website_url": "https://example.com/museum",
"latitude": null,
"longitude": null
}
]
}βββ app/ # Flutter application
β βββ lib/
β β βββ main.dart # Main app entry point
β β βββ services/
β β βββ api_service.dart # Backend API integration
β β βββ location_service.dart # GPS handling
β βββ pubspec.yaml
β
βββ backend/ # Cloudflare Workers backend
β βββ src/
β β βββ index.ts # Main Workers entry point
β β βββ types/
β β β βββ index.ts # TypeScript interfaces and types
β β βββ services/
β β β βββ auth.ts # JWT token generation/validation
β β β βββ aiService.ts # AI/LLM integration logic
β β β βββ rateLimiter.ts # Rate limiting business logic
β β βββ middleware/
β β β βββ auth.ts # JWT authentication middleware
β β β βββ rateLimiter.ts # Rate limiting middleware
β β βββ routes/
β β βββ auth.ts # Authentication endpoints
β β βββ recommendations.ts # AI recommendations endpoints
β βββ wrangler.jsonc # Workers configuration
β βββ package.json
Phase 1 (Current): Anonymous authentication, rate limiting, secure AI integration
Phase 2: Google OAuth integration, premium tiers, usage analytics
Phase 3: Cross-device sync, personalization, social features
Try asking BeMyGuide:
- "Find nearby restaurants that serve vegan food"
- "What attractions are close to Eiffel Tower that I can reach by foot?"
- "Show me fun activities for a family with 10 year old kids"
- "Where can I go shopping for local crafts?"
Rate Limiting: 30 requests/60min (configurable in backend)
AI Model: workers-ai/@cf/meta/llama-3.1-8b-instruct (Workers AI)
Deployment: wrangler deploy --env production
The app implements device-based rate limiting:
- Each device is limited to 30 requests per 60-minute window
- Rate limits are tracked in Cloudflare KV using the device ID as the key
- When rate limit is exceeded, the API returns a 429 status with a Retry-After header
- Rate limit headers are included in each response:
X-RateLimit-Limit: Maximum requests allowed in the windowX-RateLimit-Remaining: Remaining requests in the current windowX-RateLimit-Reset: Unix timestamp when the rate limit resets
To test rate limiting, make more than 30 requests within 60 minutes using the same device ID.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create feature branch (
git checkout -b feature/AmazingFeature) - Commit changes (
git commit -m 'Add AmazingFeature') - Push to branch (
git push origin feature/AmazingFeature) - Open Pull Request
This project is open source and available under the MIT License.
Jayadeep KM
- GitHub: @kmjayadeep
- Project Link: https://github.com/kmjayadeep/bemyguide
Made with β€οΈ using Flutter and Cloudflare Workers

