Quick Start
Overview
CoApi reduces HTTP client setup to its essence: define a Java or Kotlin interface with @HttpExchange methods, annotate the interface with @CoApi, and Spring Boot auto-configuration handles the rest. No manual WebClient or RestClient builders, no proxy factory setup, no boilerplate.
At a Glance
| Step | What | Key File | Source |
|---|---|---|---|
| 1. Add dependency | coapi-spring-boot-starter | spring-boot-starter/build.gradle.kts | build.gradle.kts |
| 2. Define interface | @CoApi + @GetExchange | GitHubApiClient.kt | GitHubApiClient.kt |
| 3. Configure URL | application.yaml | application.yaml | application.yaml |
| 4. Inject & use | Constructor injection | GithubController.kt | GithubController.kt |
Step 1: Add Dependency
Gradle (Kotlin DSL):
implementation("me.ahoo.coapi:coapi-spring-boot-starter")Maven:
<dependency>
<groupId>me.ahoo.coapi</groupId>
<artifactId>coapi-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>For load balancing, also add:
implementation("org.springframework.cloud:spring-cloud-starter-loadbalancer")Step 2: Define Interface
@CoApi(baseUrl = "\${github.url}")
interface GitHubApiClient {
@GetExchange("repos/{owner}/{repo}/issues")
fun getIssue(@PathVariable owner: String, @PathVariable repo: String): Flux<Issue>
}
data class Issue(val url: String)The @CoApi annotation does three things:
- Marks this interface as an HTTP client (also acts as
@Component) - Defines the base URL (supports
${...}property placeholders) - Triggers auto-configuration to register beans
Step 3: Configure URL
# application.yaml
github:
url: https://api.github.comThe ${github.url} placeholder in @CoApi(baseUrl) resolves against Spring's Environment.
Step 4: Enable & Use
Spring Boot (auto-configuration): Nothing else needed. CoApi auto-discovers @CoApi interfaces in your application's base package.
Non-Boot / explicit mode: Add @EnableCoApi:
@EnableCoApi(clients = [GitHubApiClient::class])
@SpringBootApplication
class MyApplicationInject into any component:
@RestController
class GithubController(private val gitHubApiClient: GitHubApiClient) {
@GetMapping("/issues")
fun getIssues(): Flux<Issue> {
return gitHubApiClient.getIssue("Ahoo-Wang", "CoApi")
}
}Setup Flow
flowchart TD
A["Add coapi-spring-boot-starter"] --> B["Define CoApi interface"]
B --> C["Configure URL in YAML"]
C --> D{"Spring Boot?"}
D -->|Yes| E["Auto-discovery via AutoCoApiRegistrar"]
D -->|No| F["EnableCoApi(clients=[...])"]
E --> G["CoApiRegistrar registers beans"]
F --> G
G --> H["Inject interface and call methods"]Request Flow
sequenceDiagram
autonumber
participant Controller as GithubController
participant Proxy as GitHubApiClient Proxy
participant Adapter as WebClientAdapter
participant Client as WebClient
participant API as api.github.com
Controller->>Proxy: getIssue("Ahoo-Wang", "CoApi")
Proxy->>Adapter: exchange(request)
Adapter->>Client: GET /repos/Ahoo-Wang/CoApi/issues
Client->>API: HTTP GET request
API-->>Client: JSON response
Client-->>Adapter: Flux<Issue>
Adapter-->>Proxy: Flux<Issue>
Proxy-->>Controller: Flux<Issue>Bean Registration
sequenceDiagram
autonumber
participant Boot as Spring Boot
participant Auto as AutoCoApiRegistrar
participant Reg as CoApiRegistrar
participant Registry as BeanDefinitionRegistry
Boot->>Auto: registerBeanDefinitions()
Auto->>Auto: inferClientMode()
Auto->>Registry: register HttpExchangeAdapterFactory
Auto->>Auto: scan classpath for @CoApi interfaces
Auto->>Reg: register(definitions)
loop For each @CoApi interface
Reg->>Registry: register name.HttpClient (WebClient/RestClient)
Reg->>Registry: register name.CoApi (CoApiFactoryBean proxy)
endCommon Variations
Load-Balanced Client
@CoApi(serviceId = "github-service")
interface ServiceApiClient {
@GetExchange("repos/{owner}/{repo}/issues")
fun getIssue(@PathVariable owner: String, @PathVariable repo: String): Flux<Issue>
}Configure service instances:
spring:
cloud:
discovery:
client:
simple:
instances:
github-service:
- host: api.github.com
secure: true
port: 443Synchronous Client (Java)
@CoApi(baseUrl = "${github.url}")
public interface GitHubSyncClient {
@GetExchange("repos/{owner}/{repo}/issues")
List<Issue> getIssue(@PathVariable String owner, @PathVariable String repo);
}Set coapi.mode=SYNC to switch to RestClient-based mode. Return List<T> instead of Flux<T>.
Shared API Contract Pattern
Define a shared API interface that both provider and consumer depend on:
// Shared module: example-provider-api
@HttpExchange("todo")
interface TodoApi {
@GetExchange
fun getTodo(): Flux<Todo>
}
// Consumer module
@CoApi(serviceId = "provider-service")
interface TodoClient : TodoApi
// Provider module
@RestController
class TodoController : TodoApi {
override fun getTodo(): Flux<Todo> = Flux.just(Todo("Hello"))
}Related Pages
- What is CoApi? — why CoApi exists
- Installation & Setup — detailed dependency management
- Configuration Reference — all properties
- Architecture Overview — how registration works internally
- Examples & Patterns — complete example walkthroughs
References
- GitHubApiClient example —
example/example-consumer-client/src/main/kotlin/.../GitHubApiClient.kt - ConsumerServer —
example/example-consumer-server/src/main/kotlin/.../ConsumerServer.kt - GithubController —
example/example-consumer-server/src/main/kotlin/.../GithubController.kt - Consumer application.yaml —
example/example-consumer-server/src/main/resources/application.yaml - CoApi annotation —
api/src/main/kotlin/me/ahoo/coapi/api/CoApi.kt - EnableCoApi annotation —
spring/src/main/kotlin/me/ahoo/coapi/spring/EnableCoApi.kt