Skip to content

Commit 154d889

Browse files
authored
Merge pull request #3 from johanbassonee/http4k-example
Http4k example
2 parents 6e76869 + 119fec0 commit 154d889

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3284
-5
lines changed

.gitignore

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ build/
55
!**/src/test/**/build/
66

77
### IntelliJ IDEA ###
8-
.idea/modules.xml
9-
.idea/jarRepositories.xml
10-
.idea/compiler.xml
11-
.idea/libraries/
8+
.idea/
129
*.iws
1310
*.iml
1411
*.ipr
@@ -42,4 +39,6 @@ bin/
4239
.vscode/
4340

4441
### Mac OS ###
45-
.DS_Store
42+
.DS_Store
43+
44+
CLAUDE.md

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,15 @@ This project includes a submodule that demonstrates how to build a REST API usin
133133
- **Testing**: Unit tests, integration tests, and WireMock for external service stubbing
134134

135135
The submodule provides a REST API for managing Kotlin learning resources. See the [quarkus-rest README](quarkus-rest/README.md) for more details.
136+
137+
## Http4k Submodule
138+
139+
This project is a production-ready RESTful API built with http4k demonstrating clean architecture, JWT authentication, and OpenAPI documentation.
140+
141+
- **JWT Authentication** - Secure endpoints with bearer token authentication
142+
- **OpenAPI 3.0** - Auto-generated API documentation from contract definitions
143+
- **Swagger UI** - Interactive API documentation interface
144+
- **Clean Architecture** - Domain-driven design with clear separation of concerns
145+
- **Functional Programming** - Uses Arrow-kt for functional error handling
146+
- **Comprehensive Testing** - Unit and integration tests with 121 test cases
147+
- **Code Quality** - ktlint integration for consistent code style

gradle.properties

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,17 @@ quarkusPluginVersion=3.29.0
77
quarkusPlatformGroupId=io.quarkus.platform
88
quarkusPlatformArtifactId=quarkus-bom
99
quarkusPlatformVersion=3.29.0
10+
11+
## http4k Config
12+
arrowVersion=2.1.2
13+
auth0Version=4.5.0
14+
http4kVersion=6.19.0.0
15+
jbcryptVersion=0.4
16+
kotestArrowVersion=2.0.0
17+
kotestVersion=6.0.4
18+
kotlinLoggingVersion=3.0.5
19+
kotlinSerializationVersion=1.9.0
20+
logbackVersion=1.5.19
21+
mockkVersion=1.14.6
22+
restAssuredVersion=5.5.6
23+
slf4jVersion=2.0.17

http4k-example/README.md

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# http4k Example API
2+
3+
A production-ready RESTful API built with http4k demonstrating clean architecture, JWT authentication, and OpenAPI documentation.
4+
5+
It is hard to find a good example of a production-ready REST API built with Kotlin and http4k. This project aims to fill that gap.
6+
7+
## Features
8+
9+
- **JWT Authentication** - Secure endpoints with bearer token authentication
10+
- **OpenAPI 3.0** - Auto-generated API documentation from contract definitions
11+
- **Swagger UI** - Interactive API documentation interface
12+
- **Clean Architecture** - Domain-driven design with clear separation of concerns
13+
- **Functional Programming** - Uses Arrow-kt for functional error handling
14+
- **Comprehensive Testing** - Unit and integration tests with 121 test cases
15+
- **Code Quality** - ktlint integration for consistent code style
16+
17+
## Tech Stack
18+
19+
- **Framework**: [http4k](https://www.http4k.org/) - Lightweight, functional HTTP toolkit
20+
- **Language**: Kotlin 2.1.20
21+
- **Runtime**: JVM 21
22+
- **Security**: JWT (Auth0 java-jwt), BCrypt password hashing
23+
- **Serialization**: Kotlinx Serialization
24+
- **Testing**: Kotest, MockK, REST Assured
25+
- **Observability**: Micrometer, OpenTelemetry
26+
- **Code Quality**: ktlint
27+
28+
## NOTE
29+
30+
1. The authenticate endpoint should not be taken as best practice. The intent here is to show a flow of business logic across multiple components.
31+
2. This project uses unsafe CORS settings for demonstration only. Do not use this in production.
32+
3. This project has hard coded secrets in it. Do not use hard-coded secrets in production.
33+
34+
## Features not implemented
35+
36+
- Rate limiting
37+
- Metrics
38+
- Resilience strategies
39+
40+
## Architecture
41+
42+
The project follows **Clean Architecture** principles with clear separation between layers:
43+
44+
```
45+
src/main/kotlin/za/co/ee/learning/
46+
├── domain/ # Domain layer - business logic
47+
│ ├── security/ # JWT and password providers
48+
│ ├── users/ # User domain entities
49+
│ │ └── usecases/ # Business use cases
50+
│ └── DomainError.kt # Domain error types
51+
├── infrastructure/ # Infrastructure layer
52+
│ ├── api/ # HTTP endpoints with contracts
53+
│ ├── database/ # Data persistence
54+
│ ├── routes/ # Route definitions
55+
│ └── security/ # Security implementations
56+
├── Application.kt # Main entry point
57+
├── Server.kt # Server configuration
58+
└── ServerConfig.kt # Configuration data class
59+
```
60+
61+
## Getting Started
62+
63+
### Prerequisites
64+
65+
- JDK 21 or later
66+
- Gradle (wrapper included)
67+
68+
### Installation
69+
70+
1. Clone the repository
71+
2. Navigate to the project directory:
72+
```bash
73+
cd http4k-example
74+
```
75+
76+
3. Build the project:
77+
```bash
78+
./gradlew build
79+
```
80+
81+
### Running the Application
82+
83+
Start the server:
84+
```bash
85+
./gradlew run
86+
```
87+
88+
The API will be available at `http://localhost:8080`
89+
90+
## API Endpoints
91+
92+
### Public Endpoints
93+
94+
| Method | Endpoint | Description |
95+
|--------|----------|-------------|
96+
| GET | `/health` | Health check |
97+
| POST | `/api/v1/authenticate` | Authenticate user and get JWT token |
98+
| GET | `/swagger` | Swagger UI documentation |
99+
| GET | `/openapi.json` | OpenAPI 3.0 specification |
100+
101+
### Protected Endpoints (Requires JWT)
102+
103+
| Method | Endpoint | Description |
104+
|--------|----------|-------------|
105+
| GET | `/api/v1/users` | Get all users |
106+
107+
### Authentication
108+
109+
1. **Get a JWT token:**
110+
```bash
111+
curl -X POST http://localhost:8080/api/v1/authenticate \
112+
-H "Content-Type: application/json" \
113+
-d '{"email":"[email protected]","password":"password123"}'
114+
```
115+
116+
2. **Use the token:**
117+
```bash
118+
curl http://localhost:8080/api/v1/users \
119+
-H "Authorization: Bearer YOUR_JWT_TOKEN"
120+
```
121+
122+
### Interactive Documentation
123+
124+
Visit `http://localhost:8080/swagger` to explore and test the API using Swagger UI.
125+
126+
## Testing
127+
128+
### Run All Tests
129+
```bash
130+
./gradlew test
131+
```
132+
133+
### Run Specific Test Suites
134+
```bash
135+
# Unit tests only
136+
./gradlew test --tests "za.co.ee.learning.domain.*"
137+
./gradlew test --tests "za.co.ee.learning.infrastructure.api.*"
138+
139+
# Integration tests only
140+
./gradlew test --tests "za.co.ee.learning.integration.*"
141+
```
142+
143+
### Test Coverage
144+
145+
- **Unit Tests**: 77 tests covering domain logic, infrastructure, and API layers
146+
- **Integration Tests**: 44 tests covering full HTTP stack with authentication
147+
- **Total**: 121 tests
148+
149+
## Code Quality
150+
151+
### ktlint
152+
153+
The project uses ktlint for maintaining consistent Kotlin code style.
154+
155+
**Check code style:**
156+
```bash
157+
./gradlew ktlintCheck
158+
```
159+
160+
**Auto-format code:**
161+
```bash
162+
./gradlew ktlintFormat
163+
```
164+
165+
## Configuration
166+
167+
The application can be configured via `ServerConfig` class:
168+
169+
```kotlin
170+
ServerConfig(
171+
port = 8080, // Server port
172+
jwtSecret = "your-secret", // JWT signing secret
173+
jwtIssuer = "http4k", // JWT issuer
174+
jwtExpirationSeconds = 3600, // Token expiration time
175+
enableDebugFilters = true, // Enable request/response logging
176+
enableMetrics = true // Enable Micrometer/OpenTelemetry
177+
)
178+
```
179+
180+
## Project Highlights
181+
182+
### Contract-First API Design
183+
184+
Endpoints are defined using http4k's contract DSL, which automatically generates OpenAPI documentation:
185+
186+
```kotlin
187+
val route: ContractRoute = "/api/v1/users" meta {
188+
summary = "Get Users"
189+
description = "Retrieves list of all users (requires JWT authentication)"
190+
security = BearerAuthSecurity("JWT Bearer Token")
191+
returning(Status.OK, usersResponseLens to listOf(...))
192+
} bindContract Method.GET to handler
193+
```
194+
195+
### Functional Error Handling
196+
197+
Uses Arrow-kt's `Either` type for explicit error handling:
198+
199+
```kotlin
200+
fun authenticate(request: AuthenticateRequest): DomainResult<AuthenticateResponse> =
201+
either {
202+
val user = userRepository.findByEmail(request.email)
203+
.bind()
204+
.toEither { DomainError.NotFound("User not found") }
205+
.bind()
206+
// ... continue with business logic
207+
}
208+
```
209+
210+
### JWT Security Filter
211+
212+
Conditional JWT authentication with path exclusions:
213+
214+
```kotlin
215+
JWTFilter(
216+
jwtProvider = jwtProvider,
217+
excludedPaths = setOf("/health", "/api/v1/authenticate", "/openapi.json")
218+
)
219+
```
220+
221+
## Development
222+
223+
### Project Structure
224+
225+
- `domain/` - Pure business logic, no framework dependencies
226+
- `infrastructure/api/` - HTTP endpoints with contract definitions
227+
- `infrastructure/database/` - Data access implementations
228+
- `infrastructure/routes/` - Route composition and OpenAPI generation
229+
- `infrastructure/security/` - Security implementations (JWT, BCrypt, filters)
230+
231+
### Adding a New Endpoint
232+
233+
1. Create endpoint class in `infrastructure/api/`
234+
2. Define contract with metadata
235+
3. Add route to `ContractRoutes`
236+
4. Update JWT filter exclusions if public
237+
238+
## License
239+
240+
This is an example project for learning purposes.
241+
242+
## Resources
243+
244+
- [http4k Documentation](https://www.http4k.org/)
245+
- [Arrow-kt Documentation](https://arrow-kt.io/)
246+
- [Kotest Documentation](https://kotest.io/)

http4k-example/build.gradle.kts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
3+
plugins {
4+
kotlin("jvm") version "2.1.20"
5+
id("org.jlleitschuh.gradle.ktlint") version "12.1.2"
6+
application
7+
}
8+
9+
repositories {
10+
mavenCentral()
11+
mavenLocal()
12+
}
13+
14+
val arrowVersion: String by project
15+
val auth0Version: String by project
16+
val http4kVersion: String by project
17+
val jbcryptVersion: String by project
18+
val kotestArrowVersion: String by project
19+
val kotestVersion: String by project
20+
val kotlinLoggingVersion: String by project
21+
val logbackVersion: String by project
22+
val mockkVersion: String by project
23+
val restAssuredVersion: String by project
24+
val slf4jVersion: String by project
25+
26+
dependencies {
27+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
28+
29+
// Http4k
30+
implementation(platform("org.http4k:http4k-bom:$http4kVersion"))
31+
implementation("org.http4k:http4k-core")
32+
implementation("org.http4k:http4k-server-netty")
33+
implementation("org.http4k:http4k-format-jackson")
34+
// implementation("org.http4k:http4k-client-okhttp:$http4kVersion")
35+
implementation("org.http4k:http4k-config")
36+
implementation("org.http4k:http4k-api-openapi")
37+
implementation("org.http4k:http4k-api-ui-swagger")
38+
39+
// Config
40+
implementation("com.sksamuel.hoplite:hoplite-core:2.9.0")
41+
runtimeOnly("com.sksamuel.hoplite:hoplite-yaml:2.9.0")
42+
43+
// Logging
44+
implementation("io.github.microutils:kotlin-logging-jvm:$kotlinLoggingVersion")
45+
implementation("ch.qos.logback:logback-classic:$logbackVersion")
46+
implementation("org.slf4j:slf4j-api:$slf4jVersion")
47+
48+
// Arrow-kt
49+
implementation("io.arrow-kt:arrow-core:$arrowVersion")
50+
implementation("io.arrow-kt:arrow-fx-coroutines:$arrowVersion")
51+
52+
// BCrypt
53+
implementation("org.mindrot:jbcrypt:$jbcryptVersion")
54+
55+
// JWT
56+
implementation("com.auth0:java-jwt:$auth0Version")
57+
58+
// Test dependencies
59+
testImplementation("io.rest-assured:rest-assured:$restAssuredVersion")
60+
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
61+
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
62+
testImplementation("io.kotest:kotest-property:$kotestVersion")
63+
testImplementation("io.kotest.extensions:kotest-assertions-arrow:$kotestArrowVersion")
64+
testImplementation("io.mockk:mockk:$mockkVersion")
65+
}
66+
67+
group = "za.co.ee.learning"
68+
version = "1.0-SNAPSHOT"
69+
70+
java {
71+
sourceCompatibility = JavaVersion.VERSION_21
72+
targetCompatibility = JavaVersion.VERSION_21
73+
}
74+
75+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
76+
compilerOptions.jvmTarget.set(JvmTarget.JVM_21)
77+
compilerOptions.javaParameters.set(true)
78+
}
79+
80+
tasks.test {
81+
useJUnitPlatform()
82+
}
83+
84+
ktlint {
85+
version.set("1.5.0")
86+
verbose.set(true)
87+
android.set(false)
88+
outputToConsole.set(true)
89+
ignoreFailures.set(false)
90+
filter {
91+
exclude("**/generated/**")
92+
exclude("**/build/**")
93+
}
94+
}

0 commit comments

Comments
 (0)