API Implementation
Technical details of the GraphQL API integration in the pfy CLI.
Architecture
The CLI implements a type-safe GraphQL client using struct-based queries and mutations.
Components
cli/
├── pfy/
│ └── main.go # Entry point
└── internal/
├── api/
│ ├── client.go # GraphQL client with auth
│ ├── operations.go # Type-safe operations
│ └── config.go # Configuration management
└── app/
├── app.go # CLI app setup
├── commands.go # Command definitions
└── helpers.go # Utility functionsGraphQL Client
Implementation
Built on hasura/go-graphql-client:
type Client struct {
gqlClient *graphql.Client
httpClient *http.Client
endpoint string
token string
}The client maintains both a GraphQL client for API operations and an HTTP client for REST endpoints (e.g., pilet uploads).
Authentication
Bearer token authentication via WithRequestModifier:
gqlClient = gqlClient.WithRequestModifier(func(r *http.Request) {
r.Header.Set("Authorization", "Bearer "+token)
})Type-Safe Operations
Struct-Based Queries
GraphQL operations are defined as Go structs with graphql tags:
func (c *Client) GetProjects(ctx context.Context, filters []Filter, order Order, pagination Pagination) ([]Project, error) {
var query struct {
Projects []Project `graphql:"projects(filters: $filters, order: $order, pagination: $pagination)"`
}
// ... execute query with variables
return query.Projects, nil
}Domain Types
The CLI defines domain types that map to GraphQL types:
type Project struct {
ID graphql.ID `graphql:"id" json:"id"`
Slug graphql.String `graphql:"slug" json:"slug"`
Name graphql.String `graphql:"name" json:"name"`
Description graphql.String `graphql:"description" json:"description"`
}
type Application struct {
ID graphql.ID `graphql:"id" json:"id"`
Slug graphql.String `graphql:"slug" json:"slug"`
Name graphql.String `graphql:"name" json:"name"`
Description graphql.String `graphql:"description" json:"description"`
Version graphql.String `graphql:"version" json:"version"`
}
// ... similar for Tenant, LanguagePackGraphQL Types
Type mapping from Go to GraphQL:
| Go Type | GraphQL Type | Usage |
|---|---|---|
graphql.String | String | Text fields |
graphql.ID | ID | Unique identifiers |
graphql.Int | Int | Integer numbers |
graphql.Boolean | Boolean | True/false values |
graphql.Float | Float | Decimal numbers |
All domain types use graphql tags for field mapping and json tags for JSON serialization.
Mutations
Mutations use input types:
type CreateProjectInput struct {
Slug graphql.String `json:"slug"`
Name graphql.String `json:"name"`
Description graphql.String `json:"description,omitempty"`
}
func (c *Client) CreateProject(ctx context.Context, input CreateProjectInput) (*Project, error) {
var mutation struct {
CreateProject Project `graphql:"createProject(input: $input)"`
}
// ... execute mutation
return &mutation.CreateProject, nil
}Application Promotion and Copying
Special mutations for application deployment workflows:
type PromoteApplicationInput struct {
ApplicationID graphql.ID `json:"applicationId"`
TargetTenantID graphql.ID `json:"targetTenantId"`
Slug graphql.String `json:"slug"`
Name graphql.String `json:"name,omitempty"`
Version graphql.String `json:"version,omitempty"`
}
type CopyApplicationInput struct {
SourceApplicationID graphql.ID `json:"sourceApplicationId"`
TargetTenantID graphql.ID `json:"targetTenantId"`
Slug graphql.String `json:"slug"`
// ... other fields
}Implemented Operations
Projects
Queries:
GetProjects- List projects with filters, order, paginationGetProject- Get project by slug
Mutations:
CreateProject- Create new projectUpdateProject- Update existing project (not exposed in CLI)DeleteProject- Delete project (not exposed in CLI)
Tenants
Queries:
GetTenants- List tenants in projectGetTenant- Get tenant by project ID and slug
Mutations:
CreateTenant- Create tenant in projectUpdateTenant- Update tenant (not exposed in CLI)DeleteTenant- Delete tenant (not exposed in CLI)
Applications
Queries:
GetApplications- List applications in tenantGetApplication- Get application by tenant ID and slug
Mutations:
CreateApplication- Create application in tenantUpdateApplication- Update application (not exposed in CLI)DeleteApplication- Delete application (not exposed in CLI)
Language Packs
Queries:
GetLanguagePacks- List language packs in project
Mutations:
CreateLanguagePack- Create language pack (planned)DeleteLanguagePack- Delete language pack (planned)
Filters, Ordering, and Pagination
Filter, Order, and Pagination
type Filter struct {
Field graphql.String `json:"field"`
Operator graphql.String `json:"operator"`
Value interface{} `json:"value"`
}
type Order struct {
Field graphql.String `json:"field"`
Direction graphql.String `json:"direction"` // "asc" or "desc"
}
type Pagination struct {
Offset graphql.Int `json:"offset"`
Limit graphql.Int `json:"limit"`
}Configuration Management
Config Structure
type Config struct {
ManagerURL string `yaml:"manager_url"`
Token string `yaml:"token"`
}Configuration is loaded from ~/.pfy/config.yaml and can be overridden by environment variables (PFY_MANAGER_URL, PFY_MANAGER_TOKEN).
Configuration Priority
- Command-line flags (highest priority)
- Environment variables
- Config file (lowest priority)
Error Handling
All operations wrap errors with context:
if err := c.Query(ctx, &query, variables); err != nil {
return nil, fmt.Errorf("failed to get projects: %w", err)
}GraphQL errors include field paths and detailed messages for debugging.
REST Endpoints
Pilet Upload
The client includes an UploadPilet method for uploading pilet packages via multipart/form-data:
func (c *Client) UploadPilet(ctx context.Context, projectID, name, version, description, filePath string) error {
// Creates multipart form with file upload
// POST /pilets/{projectID}/{name}/{version}
}Best Practices
- Use struct tags - Define GraphQL fields with
graphqltags - Type safety - Leverage Go's type system with GraphQL types
- Error wrapping - Provide context with error messages
- Configuration hierarchy - Support flags, env vars, and config files
- Secure by default - Use HTTPS and verify certificates
- Token security - Store tokens with restricted permissions (0600)
- Verbose mode - Provide detailed output for debugging
- Quiet mode - Enable scripting with minimal output
Future Enhancements
- [ ] Batch operations for multiple resources
- [ ] Advanced filtering and search
- [ ] Export/import configurations
- [ ] Offline mode with caching
- [ ] GraphQL subscription support
- [ ] Custom output formats (JSON, YAML, table)
- [ ] Shell completion for resource names
- [ ] Interactive mode for complex operations