The Keyline API Client is a Go package that provides a convenient, type-safe way to interact with the Keyline API. It's designed to simplify building custom tools or integrations.
The client is built on three core components:
- Client - Main entry point that provides access to resource-specific clients
- Transport - Handles HTTP communication, request/response processing, and virtual server routing
- Resource Clients - Specialized clients for different API resources (e.g., ApplicationClient)
The client is part of the Keyline repository and can be used directly:
import "Keyline/client"package main
import (
"Keyline/client"
"Keyline/internal/handlers"
"context"
"fmt"
)
func main() {
// Create a new client
c := client.NewClient(
"http://localhost:8081", // Base URL of Keyline API
"my-virtual-server", // Virtual server name
)
ctx := context.Background()
// Create an application
app, err := c.Application().Create(ctx, handlers.CreateApplicationRequestDto{
Name: "my-app",
DisplayName: "My Application",
RedirectUris: []string{"http://localhost:3000/callback"},
PostLogoutUris: []string{"http://localhost:3000/logout"},
Type: "public",
})
if err != nil {
panic(err)
}
fmt.Printf("Created application with ID: %s\n", app.Id)
}The Transport handles low-level HTTP communication:
- URL Construction: Automatically constructs full URLs with virtual server routing
- Request Building: Creates properly formatted HTTP requests
- Error Handling: Converts HTTP errors into Go errors
- Customization: Supports custom HTTP clients and middleware via options
All API requests are automatically routed through the virtual server path:
Base URL: http://localhost:8081
Virtual Server: my-virtual-server
Endpoint: /applications
Result: http://localhost:8081/api/virtual-servers/my-virtual-server/applications
The client supports several configuration options:
Provide your own http.Client for custom timeouts, TLS configuration, etc.:
import (
"net/http"
"time"
)
httpClient := &http.Client{
Timeout: 30 * time.Second,
}
c := client.NewClient(
"http://localhost:8081",
"my-virtual-server",
client.WithClient(httpClient),
)Add authentication, logging, or other middleware:
// Authentication middleware
authMiddleware := func(next http.RoundTripper) http.RoundTripper {
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
// Add bearer token
req.Header.Set("Authorization", "Bearer "+token)
return next.RoundTrip(req)
})
}
c := client.NewClient(
"http://localhost:8081",
"my-virtual-server",
client.WithRoundTripper(authMiddleware),
)
// Helper type for function-based round trippers
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
return f(r)
}Override the base URL at runtime:
c := client.NewClient(
"http://localhost:8081",
"my-virtual-server",
client.WithBaseURL("https://api.example.com"),
)The client returns structured errors that can be inspected:
app, err := c.Application().Create(ctx, createDto)
if err != nil {
// Check for API errors
if apiErr, ok := err.(client.ApiError); ok {
fmt.Printf("API Error: %s (HTTP %d)\n", apiErr.Message, apiErr.Code)
switch apiErr.Code {
case 401:
// Handle unauthorized
case 403:
// Handle forbidden
case 404:
// Handle not found
default:
// Handle other errors
}
} else {
// Handle other types of errors (network, etc.)
fmt.Printf("Error: %v\n", err)
}
}Here's a complete example showing authentication and error handling:
package main
import (
"context"
"fmt"
"net/http"
"Keyline/client"
"Keyline/internal/handlers"
)
func main() {
// Create authenticated client
token := "your-bearer-token"
c := client.NewClient(
"http://localhost:8081",
"my-virtual-server",
client.WithRoundTripper(authMiddleware(token)),
)
ctx := context.Background()
// Create an application
app, err := c.Application().Create(ctx, handlers.CreateApplicationRequestDto{
Name: "example-app",
DisplayName: "Example Application",
RedirectUris: []string{"http://localhost:3000/callback"},
PostLogoutUris: []string{"http://localhost:3000/logout"},
Type: "public",
})
if err != nil {
handleError(err)
return
}
fmt.Printf("✓ Created application: %s (ID: %s)\n", app.Name, app.Id)
// List all applications
apps, err := c.Application().List(ctx, client.ListApplicationParams{
Page: 1,
Size: 10,
})
if err != nil {
handleError(err)
return
}
fmt.Printf("✓ Found %d applications\n", len(apps.Items))
for _, app := range apps.Items {
fmt.Printf(" - %s: %s\n", app.Name, app.DisplayName)
}
}
// authMiddleware adds bearer token authentication (just a readme example, not for production use)
func authMiddleware(token string) client.TransportOptions {
return client.WithRoundTripper(func(next http.RoundTripper) http.RoundTripper {
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+token)
return next.RoundTrip(req)
})
})
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
return f(r)
}
func handleError(err error) {
if apiErr, ok := err.(client.ApiError); ok {
fmt.Printf("✗ API Error: %s (HTTP %d)\n", apiErr.Message, apiErr.Code)
} else {
fmt.Printf("✗ Error: %v\n", err)
}
}The client is designed to be extensible. Additional resource clients can be added by:
- Creating a new interface in the client package
- Implementing the interface with a struct that uses the Transport
- Adding a method to the main Client interface to access the new resource client
Example structure for adding a User client:
type UserClient interface {
Create(ctx context.Context, dto handlers.CreateUserRequestDto) (handlers.CreateUserResponseDto, error)
Get(ctx context.Context, id uuid.UUID) (handlers.GetUserResponseDto, error)
// ... other methods
}
// In client.go
func (c *client) User() UserClient {
return NewUserClient(c.transport)
}- Always use context: Pass a proper context for cancellation and timeout support
- Handle errors properly: Check for both API errors and network errors
- Reuse clients: Create one client instance and reuse it across requests
- Use authentication middleware: Add bearer tokens or basic auth via round trippers
- Test with the client: Use it in integration tests for realistic API interactions
- Configure timeouts: Set appropriate HTTP client timeouts for your use case
- E2E Tests Documentation - Learn how the client is used in end-to-end tests
- API Documentation - Complete API reference
- Main README - Project overview and setup
client/
├── README.md # This file
├── client.go # Main client interface
├── transport.go # HTTP transport layer
├── application.go # Application resource client
└── application_test.go # Unit tests
When adding new resource clients:
- Create the interface in a new file (e.g.,
user.go) - Implement all CRUD operations following the Application client pattern
- Add comprehensive unit tests using httptest
- Update the main Client interface to expose the new resource client
- Document the new client in this README
For questions or issues, please refer to the main project documentation or open an issue on GitHub.