{"id":51685415,"url":"https://github.com/gautam84/foodike-backend","last_synced_at":"2026-07-15T19:34:37.574Z","repository":{"id":364543245,"uuid":"1205211551","full_name":"gautam84/foodike-backend","owner":"gautam84","description":"🍔 A microservice-ready modular monolith backend for a food delivery app built with Kotlin \u0026 Ktor. Clean architecture with explicit extraction seams - event bus, query ports \u0026 per-module migrations - scale from one JAR to independent services without rewriting business logic. 🚀","archived":false,"fork":false,"pushed_at":"2026-06-13T11:51:55.000Z","size":172,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T13:21:06.798Z","etag":null,"topics":["backend","clean-architecture","event-driven","exposed","food-delivery","kotlin","kotlin-coroutines","ktor","microservices","modular-monolith","open-source","postgresql","redis","rest-api","websockets"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gautam84.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T18:39:56.000Z","updated_at":"2026-06-13T11:51:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gautam84/foodike-backend","commit_stats":null,"previous_names":["gautam84/foodike-backend"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/gautam84/foodike-backend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gautam84%2Ffoodike-backend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gautam84%2Ffoodike-backend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gautam84%2Ffoodike-backend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gautam84%2Ffoodike-backend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gautam84","download_url":"https://codeload.github.com/gautam84/foodike-backend/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gautam84%2Ffoodike-backend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35519051,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-15T02:00:06.706Z","response_time":131,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","clean-architecture","event-driven","exposed","food-delivery","kotlin","kotlin-coroutines","ktor","microservices","modular-monolith","open-source","postgresql","redis","rest-api","websockets"],"created_at":"2026-07-15T19:34:36.857Z","updated_at":"2026-07-15T19:34:37.568Z","avatar_url":"https://github.com/gautam84.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Foodike Backend\n\nA microservice-ready modular monolith backend for a food delivery app, built with Kotlin and Ktor. It is being developed as a reference architecture that can scale from a single deploy to independent microservices without rewriting business logic.\n\n## Why This Exists\n\nMost food delivery backend tutorials are either too simple (single-file CRUD) or jump straight to microservices (overkill for 99% of projects). This project sits in the sweet spot: a production-grade modular monolith where every module is shaped like a future microservice, with explicit extraction seams documented and enforced by Gradle.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│                   Single JVM Process                │\n│                                                     │\n│  ┌─────────────────────────────────────────────┐    │\n│  │              shared/                        │    │\n│  │   common · events · auth · persistence      │    │\n│  └─────────────────────────────────────────────┘    │\n│                                                     │\n│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │\n│  │  user    │ │restaurant│ │  order   │            │\n│  │ service  │ │ service  │ │ service  │            │\n│  └──────────┘ └──────────┘ └──────────┘            │\n│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │\n│  │ payment  │ │ notific. │ │ tracking │            │\n│  │ service  │ │ service  │ │ service  │            │\n│  └──────────┘ └──────────┘ └──────────┘            │\n│         │            │            │                 │\n│  ┌─────────────────────────────────────────────┐    │\n│  │         EventBus (SharedFlow)               │    │\n│  └─────────────────────────────────────────────┘    │\n└─────────────────────────────────────────────────────┘\n```\n\nThe target architecture is for each service module to contain its own `domain/`, `infrastructure/`, `api/`, `di/`, and `migrations/`. The current repository has a lighter scaffold in place and is moving toward that structure incrementally.\n\n## Current Status\n\nImplemented today:\n- Multi-module Gradle build\n- Shared `common`, `events`, `auth`, and `persistence` modules\n- Service module boundaries for user, restaurant, order, payment, notification, and tracking\n- Ktor application shell in `app/`\n- Placeholder routes and minimal domain models\n- Basic integration test for app startup\n\nPlanned but not implemented yet:\n- PostgreSQL/PostGIS setup\n- Redis integration\n- Flyway migrations\n- Razorpay, Firebase, and S3 integrations\n- Swagger/OpenAPI docs\n- Micrometer/Prometheus metrics\n- Full domain services, repositories, DTOs, and production flows\n- Kotest, MockK, and Testcontainers-based test suite\n\n## Tech Stack\n\n| Layer | Tool |\n|---|---|\n| Language | Kotlin 2.x |\n| Framework | Ktor 3.x (Netty) |\n| Serialization | kotlinx.serialization |\n| Database | H2 today, PostgreSQL/PostGIS planned |\n| ORM | Exposed DSL |\n| Migrations | Planned |\n| Cache | Planned |\n| DI | Koin 4 |\n| Event Bus | In-process SharedFlow (swappable to RabbitMQ) |\n| Auth | JWT (java-jwt) |\n| Payments | Planned |\n| Push Notifications | Planned |\n| Storage | Planned |\n| Observability | Logback today, broader observability planned |\n| Testing | Ktor `testApplication` today, broader test stack planned |\n\n## Getting Started\n\n### Prerequisites\n\n- JDK 17+\n- No external infrastructure is required for the current scaffold\n\n### Run Locally\n\n```bash\n# Clone the repo\ngit clone https://github.com/gautam84/foodike-backend.git\ncd foodike-backend\n\n# Run the application\n./gradlew :app:run\n```\n\nThe server starts at `http://localhost:8080`.\n\n### Environment Variables\n\nThe current scaffold only uses application config from `app/src/main/resources/application.yaml`.\n\nPlanned environment variables for future infrastructure:\n\n| Variable | Description | Required |\n|---|---|---|\n| `DB_URL` | Postgres JDBC URL | Planned |\n| `DB_USER` | Postgres username | Planned |\n| `DB_PASSWORD` | Postgres password | Planned |\n| `REDIS_URL` | Redis connection URL | Planned |\n| `JWT_SECRET` | Secret for signing JWTs | Planned |\n| `JWT_ISSUER` | JWT issuer claim | Planned |\n| `RAZORPAY_KEY` | Razorpay API key | For payments |\n| `RAZORPAY_SECRET` | Razorpay API secret | For payments |\n| `FIREBASE_CREDENTIALS` | Path to Firebase service account JSON | For notifications |\n| `S3_BUCKET` | S3 bucket name for image uploads | For storage |\n| `S3_REGION` | AWS region | For storage |\n\n### Run Tests\n\n```bash\n# Unit tests\n./gradlew :tests:test --tests \"com.example.foodike.unit.*\"\n\n# All tests\n./gradlew :tests:test\n```\n\n## Project Structure\n\n```\nfoodike-backend/\n├── shared/\n│   ├── common/          # Value objects, exceptions, utilities\n│   ├── events/          # Event contracts + EventBus interface\n│   ├── auth/            # JWT validation, shared across all modules\n│   └── persistence/     # Database factory, query helpers\n│\n├── services/\n│   ├── user-service/          # Auth (OTP), profiles, addresses\n│   ├── restaurant-service/    # Restaurants, menus, search\n│   ├── order-service/         # Cart, orders, coupons\n│   ├── payment-service/       # Razorpay integration\n│   ├── notification-service/  # FCM, SMS, email\n│   └── tracking-service/      # WebSocket order tracking\n│\n├── app/                 # Ktor entry point, plugin config, Koin wiring\n└── tests/               # Unit + integration tests\n```\n\nTarget per-service structure:\n\n```\nservices/xyz-service/\n├── domain/\n│   ├── model/           # Entities and value objects\n│   ├── repository/      # Interfaces (ports)\n│   ├── service/         # Business logic\n│   └── port/            # Cross-service query interfaces (if needed)\n├── infrastructure/\n│   ├── persistence/\n│   │   ├── tables/      # Exposed table definitions\n│   │   └── *Impl.kt    # Repository implementations\n│   ├── adapter/         # In-process adapters for query ports\n│   └── events/          # Event publishers and consumers\n├── api/\n│   ├── routes/          # Ktor route definitions\n│   ├── dto/             # Request/response objects\n│   └── mapper/          # Domain ↔ DTO mappers\n├── di/\n│   └── XyzModule.kt    # Koin module\n└── migrations/\n    └── db/migration/    # Flyway SQL files\n```\n\n## Module Dependency Rules\n\n```\nshared/common        ← no dependencies\nshared/events        ← common\nshared/auth          ← common\nshared/persistence   ← common\n\nservices/*           ← shared modules only (NEVER other services)\n\napp                  ← all services (composition root)\n```\n\n**The critical rule:** service modules should never depend on each other. The project is structured around that rule, and module dependencies should continue to preserve it. If `order-service` needs data from `restaurant-service`, it should go through a query port interface rather than a direct import.\n\n## Three Extraction Seams\n\nThis architecture is intended so that extracting any module into an independent microservice can happen with minimal business-logic changes. These seams are part of the target design and are only partially implemented today:\n\n### 1. Event Bus\n\nServices communicate asynchronously through `EventBus`. Today the repo includes an in-process event bus. A broker-backed event bus is planned for extraction scenarios.\n\n```kotlin\n// Today (monolith)\nsingle\u003cEventBus\u003e { InProcessEventBus() }\n\n// Planned after extraction\nsingle\u003cEventBus\u003e { RabbitMqEventBus(connectionFactory) }\n```\n\n### 2. Query Ports\n\nWhen a service needs synchronous data from another service, it should call a port interface. The concrete in-process and HTTP adapters shown below are target examples and are not fully implemented yet.\n\n```kotlin\n// Target monolith form\nsingle\u003cMenuQueryPort\u003e { InProcessMenuAdapter(get()) }\n\n// Planned extraction form\nsingle\u003cMenuQueryPort\u003e { HttpMenuClient(httpClient, env(\"RESTAURANT_SERVICE_URL\")) }\n```\n\n### 3. Per-Module Migrations\n\nEach service is intended to own its own migrations. Flyway integration is planned and not wired into the current scaffold yet.\n\n### Extraction Checklist\n\nTo extract any service into an independent microservice:\n\n1. Copy `services/xyz-service/` into its own Gradle project\n2. Add its own `Application.kt` and `Dockerfile`\n3. Swap `InProcessEventBus` → `RabbitMqEventBus`\n4. Swap `InProcess*Adapter` → `Http*Client` for query ports\n5. Point Flyway at a dedicated database\n6. Add to API gateway route config\n7. Remove from monolith's `settings.gradle.kts` and `AppModule.kt`\n\n## API Overview\n\n### Auth\n- `POST /auth/send-otp` — send OTP to phone\n- `POST /auth/verify-otp` — verify and get JWT pair\n- `POST /auth/refresh` — refresh access token\n\n### Users\n- `GET /users/me` — current profile\n- `PUT /users/me` — update profile\n- `GET /users/me/addresses` — list addresses\n- `POST /users/me/addresses` — add address\n- `DELETE /users/me/addresses/{id}` — remove address\n\n### Restaurants\n- `GET /restaurants` — list (paginated, filterable)\n- `GET /restaurants/nearby?lat=\u0026lng=\u0026radius=` — geo query\n- `GET /restaurants/{id}` — detail\n- `GET /restaurants/{id}/menu` — grouped menu\n\n### Cart\n- `GET /cart` — current cart\n- `POST /cart/items` — add item\n- `PUT /cart/items/{id}` — update quantity\n- `DELETE /cart/items/{id}` — remove item\n- `DELETE /cart` — clear\n- `POST /cart/coupon` — apply coupon\n\n### Orders\n- `POST /orders` — place order\n- `GET /orders` — list (paginated)\n- `GET /orders/{id}` — detail\n- `PATCH /orders/{id}/status` — update status\n- `WS /track/{orderId}` — live tracking\n\n### Payments\n- `POST /payments/initiate` — create Razorpay order\n- `POST /payments/verify` — verify payment signature\n\n### Search\n- `GET /search?q=\u0026type=` — full-text search\n\nThe endpoint list above reflects the target API surface. The current implementation only exposes a small subset of placeholder routes while the modules are being built out.\n\n## License\n\nThis project is licensed under the MIT License. See [LICENSE.md](LICENSE.md) for details.\n\n## Contributing\n\nContributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a PR.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgautam84%2Ffoodike-backend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgautam84%2Ffoodike-backend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgautam84%2Ffoodike-backend/lists"}