HTTP Client Generation
Typo generates type-safe HTTP clients that implement the same API interface as the server. This ensures client and server stay in sync.
JDK HttpClient (Java/Kotlin)
A lightweight client using Java's built-in HttpClient:
Java
public class PetsApiClient implements PetsApi {
private final HttpClient httpClient;
private final URI baseUri;
private final ObjectMapper objectMapper;
public PetsApiClient(HttpClient httpClient, URI baseUri, ObjectMapper objectMapper) {
this.httpClient = httpClient;
this.baseUri = baseUri;
this.objectMapper = objectMapper;
}
@Override
public Response200404<Pet, Error> getPet(PetId petId) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUri + "/pets/" + petId))
.GET()
.header("Accept", "application/json")
.build();
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
int statusCode = response.statusCode();
if (statusCode == 200) {
return new Ok<>(objectMapper.readValue(response.body(), Pet.class));
} else if (statusCode == 404) {
return new NotFound<>(objectMapper.readValue(response.body(), Error.class));
} else {
throw new IllegalStateException("Unexpected status: " + statusCode);
}
}
}
Kotlin
data class PetsApiClient(
val httpClient: HttpClient,
val baseUri: URI,
val objectMapper: ObjectMapper
) : PetsApi {
override fun getPet(petId: PetId): Response200404<Pet, Error> {
val request = HttpRequest.newBuilder(URI.create("$baseUri/pets/$petId"))
.GET()
.header("Accept", "application/json")
.build()
val response = httpClient.send(request, BodyHandlers.ofString())
val statusCode = response.statusCode()
return when (statusCode) {
200 -> Ok(objectMapper.readValue(response.body(), Pet::class.java))
404 -> NotFound(objectMapper.readValue(response.body(), Error::class.java))
else -> throw IllegalStateException("Unexpected status: $statusCode")
}
}
}
Reactive Clients (Quarkus)
For Quarkus, clients return Uni<T> for non-blocking I/O:
public Uni<Response200404<Pet, Error>> getPet(PetId petId) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUri + "/pets/" + petId))
.GET()
.build();
return Uni.createFrom()
.completionStage(() -> httpClient.sendAsync(request, BodyHandlers.ofString()))
.map(response -> {
int statusCode = response.statusCode();
if (statusCode == 200) {
return new Ok<>(objectMapper.readValue(response.body(), Pet.class));
} else if (statusCode == 404) {
return new NotFound<>(objectMapper.readValue(response.body(), Error.class));
} else {
throw new IllegalStateException("Unexpected status: " + statusCode);
}
});
}
Http4s Client (Scala)
Functional Scala with Cats Effect:
class PetsApiClient(client: Client[IO], baseUri: Uri) extends PetsApi {
override def getPet(petId: PetId): IO[Response200404[Pet, Error]] = {
val request = Request[IO](Method.GET, baseUri / "pets" / petId)
client.run(request).use { response =>
val statusCode = response.status.code
if (statusCode == 200)
response.as[Pet].map(Ok(_))
else if (statusCode == 404)
response.as[Error].map(NotFound(_))
else
IO.raiseError(new IllegalStateException(s"Unexpected status: $statusCode"))
}
}
}
Shared Interface
Both client and server implement the same interface:
public interface PetsApi {
Response200404<Pet, Error> getPet(PetId petId);
}
// Server implements it
public class PetsApiServerImpl implements PetsApiServer { ... }
// Client implements it
public class PetsApiClient implements PetsApi { ... }
This enables powerful patterns:
- Contract testing - Verify client and server agree
- In-process testing - Run integration tests without HTTP
- Mocking - Easily mock the API for unit tests