Adding a New API Adapter
January 30, 2026 ยท View on GitHub
Guide for implementing new Nylas API adapters.
Overview
Adapters implement the ports.NylasClient interface and handle HTTP communication with the Nylas API.
Location: internal/adapters/nylas/
Steps
1. Update Port Interface
Add methods to internal/ports/nylas.go:
type NylasClient interface {
// ... existing methods
// New feature methods
GetResource(ctx context.Context, id string) (*domain.Resource, error)
ListResources(ctx context.Context, params *domain.ResourceParams) ([]*domain.Resource, error)
}
2. Implement Adapter
Create internal/adapters/nylas/resource.go:
package nylas
import (
"context"
"fmt"
"net/http"
)
func (c *Client) GetResource(ctx context.Context, id string) (*domain.Resource, error) {
url := fmt.Sprintf("%s/v3/resources/%s", c.baseURL, id)
req, err := c.newRequest(ctx, http.MethodGet, url, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
var resource domain.Resource
if err := c.do(req, &resource); err != nil {
return nil, fmt.Errorf("failed to get resource: %w", err)
}
return &resource, nil
}
3. Add to Mock
Update internal/adapters/nylas/mock.go:
type MockClient struct {
GetResourceFunc func(ctx context.Context, id string) (*domain.Resource, error)
}
func (m *MockClient) GetResource(ctx context.Context, id string) (*domain.Resource, error) {
if m.GetResourceFunc != nil {
return m.GetResourceFunc(ctx, id)
}
return nil, nil
}
4. Write Tests
func TestGetResource(t *testing.T) {
mock := &MockClient{
GetResourceFunc: func(ctx context.Context, id string) (*domain.Resource, error) {
return &domain.Resource{ID: id}, nil
},
}
resource, err := mock.GetResource(context.Background(), "test-id")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resource.ID != "test-id" {
t.Errorf("expected ID 'test-id', got '%s'", resource.ID)
}
}
HTTP Client Patterns
GET Requests
func (c *Client) Get(ctx context.Context, id string) (*domain.Object, error) {
url := fmt.Sprintf("%s/v3/objects/%s", c.baseURL, id)
req, err := c.newRequest(ctx, http.MethodGet, url, nil, nil)
if err != nil {
return nil, err
}
var obj domain.Object
return &obj, c.do(req, &obj)
}
POST Requests
func (c *Client) Create(ctx context.Context, obj *domain.Object) (*domain.Object, error) {
url := fmt.Sprintf("%s/v3/objects", c.baseURL)
req, err := c.newRequest(ctx, http.MethodPost, url, nil, obj)
if err != nil {
return nil, err
}
var created domain.Object
return &created, c.do(req, &created)
}
Query Parameters
func (c *Client) List(ctx context.Context, params *domain.ListParams) ([]*domain.Object, error) {
url := fmt.Sprintf("%s/v3/objects", c.baseURL)
query := make(map[string]string)
if params.Limit > 0 {
query["limit"] = fmt.Sprintf("%d", params.Limit)
}
if params.Offset > 0 {
query["offset"] = fmt.Sprintf("%d", params.Offset)
}
req, err := c.newRequest(ctx, http.MethodGet, url, query, nil)
if err != nil {
return nil, err
}
var response struct {
Data []*domain.Object `json:"data"`
}
return response.Data, c.do(req, &response)
}
Error Handling
// Wrap all errors with context
if err != nil {
return nil, fmt.Errorf("failed to <operation>: %w", err)
}
// Check HTTP status codes
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
Testing Adapters
- Unit tests - Test with mocks
- Integration tests - Test with real API
- Error cases - Test failure paths
- Edge cases - Empty responses, large datasets
More Resources
- Port Interface:
internal/ports/nylas.go - Existing Adapters:
internal/adapters/nylas/ - Testing Guide: testing-guide.md