This project is a Kotlin Multiplatform implementation of the RealWorld application, providing a medium.com clone with both backend and frontend components. It demonstrates how to build a full-stack application using Kotlin across multiple platforms.
The project is organized into several modules:
A Ktor-based server that implements the RealWorld API specification, providing endpoints for user authentication, profiles, articles, comments, and more.
A Kotlin Multiplatform library that defines the API contract (resources, data models) shared between the backend and client applications. This enables type-safe API communication across all platforms.
A Kotlin Multiplatform UI application split into platform app modules and a shared Compose Multiplatform module:
app/sharedcontains shared UI and client logicapp/androidAppcontains the Android app hostapp/desktopAppcontains the Desktop (JVM) app hostapp/webAppcontains the Web app hostapp/iosAppcontains the native iOS app that integrates with the shared Kotlin framework
- Kotlin - Programming language
- Ktor - Web framework
- Exposed - SQL framework
- PostgreSQL - Database
- JWT - Authentication
- Argon2 - Password hashing
- Flyway - Database migrations
- Micrometer - Metrics collection
- Prometheus - Monitoring
- Kotlin Multiplatform - Cross-platform Kotlin
- Compose Multiplatform - UI framework
- Kotlin Coroutines - Asynchronous programming
- KotlinX Serialization - JSON serialization
- KotlinX DateTime - Date and time handling
For detailed development guidelines, please refer to the Guidelines document. These guidelines cover:
- Kotlin coding practices
- Architecture and design principles
- Database and resource management
- Testing strategies
- Code style and documentation
- Concurrency and performance considerations
- Kotlin Multiplatform Documentation
- Compose Multiplatform Documentation
- Ktor Documentation
- RealWorld API Spec
The backend implements the following features from the RealWorld API specification:
- Registration:
POST /users- Register a new user - Login:
POST /users/login- Login a user - Current User:
GET /user- Get the current user - Update User:
PUT /user- Update the current user
- Get Profile:
GET /profiles/{username}- Get a user's profile - Follow User:
POST /profiles/{username}/follow- Follow a user - Unfollow User:
DELETE /profiles/{username}/follow- Unfollow a user
- Create Article:
POST /articles- Create a new article - List Articles:
GET /articles- List articles with filtering - Feed Articles:
GET /articles/feed- Get articles from followed users - Get Article:
GET /articles/{slug}- Get an article - Update Article:
PUT /articles/{slug}- Update an article - Delete Article:
DELETE /articles/{slug}- Delete an article
- Add Comment:
POST /articles/{slug}/comments- Add a comment to an article - Get Comments:
GET /articles/{slug}/comments- Get comments for an article - Delete Comment:
DELETE /articles/{slug}/comments/{id}- Delete a comment
- Favorite Article:
POST /articles/{slug}/favorite- Favorite an article - Unfavorite Article:
DELETE /articles/{slug}/favorite- Unfavorite an article
- Get Tags:
GET /tags- Get popular tags
- JDK 11 or higher
- Android SDK (for Android development)
- Xcode (for iOS development)
- Docker and Docker Compose (for running PostgreSQL)
The backend uses PostgreSQL as the database. You can start it using Docker Compose:
docker-compose up -dThis will start a PostgreSQL instance with the configuration specified in the docker-compose.yaml file.
For local manual testing, there is a seeded dataset at backend/src/main/resources/db/manual-test-data.sql.
It adds:
- 10 demo users
- 30 articles
- tags, follows, favorites, and comments
Default password for all demo users: password123
The docker-compose.yaml file includes a db-seed service that waits for the Flyway-created schema and then imports this dataset.
Important: Flyway migrations run in the backend application, so you still need to start the backend first (or otherwise ensure the schema already exists) before the seed service can succeed.
If you want to load it manually, for example:
psql postgresql://ktor_user:ktor_password@localhost:5432/ktor_sample \
-f backend/src/main/resources/db/manual-test-data.sqlTo build and run the backend, use one of the following Gradle tasks:
| Task | Description |
|---|---|
./gradlew :backend:test |
Run the backend tests |
./gradlew :backend:build |
Build the backend |
./gradlew :backend:run |
Run the backend server |
./gradlew :backend:buildFatJar |
Build an executable JAR with all dependencies included |
To run the Compose apps on different platforms:
| Task | Description |
|---|---|
./gradlew :app:androidApp:assembleDebug |
Build the Android app |
./gradlew :app:desktopApp:run |
Run on desktop |
./gradlew :app:webApp:wasmJsBrowserDevelopmentRun |
Run in browser (WebAssembly) |
Open the app/iosApp/iosApp.xcodeproj file in Xcode and run the application from there.
The backend can be configured using environment variables or by modifying the application.yaml file in the backend/src/main/resources directory.
Key configuration properties:
database.host: PostgreSQL host (default: localhost)database.port: PostgreSQL port (default: 5432)database.name: Database name (default: realworld)database.username: Database username (default: postgres)database.password: Database password (default: postgres)jwt.secret: Secret key for JWT token generationjwt.issuer: Issuer for JWT tokensjwt.audience: Audience for JWT tokensjwt.realm: Realm for JWT authentication
Once the backend server is running, you can access the API at http://localhost:8080. The API follows the RealWorld API specification.