Skip to main content
This guide explains the workflow for contributing code changes to the Address API.

Development workflow

1. Start the server locally

Before making changes, ensure the server runs locally:
# Start PostgreSQL
brew services start postgresql@15  # macOS
# or
sudo systemctl start postgresql    # Linux

# Start the server
go run main.go serve
Verify it’s working:
curl http://localhost:8080/healthz

2. Create a new branch

git checkout -b feature/your-feature-name
Branch naming conventions:
  • feature/ - New features
  • fix/ - Bug fixes
  • refactor/ - Code refactoring
  • docs/ - Documentation changes

3. Write tests first (TDD approach)

Before implementing your feature, write tests that define the expected behavior.

Add integration tests

Create or update test files in tests/integration/:
func TestYourNewFeature(t *testing.T) {
    // Setup
    e, store := setupTestServer(t)
    defer store.ConnPool.Close()

    // Create test data
    apiKey := createTestAPIKey(t, store, []string{"GEOCODE"})

    // Make request
    req := httptest.NewRequest(http.MethodPost, "/api/v1/your-endpoint",
        strings.NewReader(`{"field":"value"}`))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("x-commenda-key", apiKey)

    rec := httptest.NewRecorder()
    e.ServeHTTP(rec, req)

    // Assert
    assert.Equal(t, http.StatusOK, rec.Code)
    // Add more assertions
}

Run tests (they should fail)

go run . tests
Expected output:
FAIL: TestYourNewFeature
This confirms your tests are working and the feature isn’t implemented yet.

4. Implement the feature

Now implement the actual feature to make the tests pass.

Example: Adding a new endpoint

Step 1: Define the DTO (Data Transfer Object) Create internal/your_feature/dto/your_dto.go:
package dto

type YourRequest struct {
    Field string `json:"field" validate:"required,min=1"`
}

type YourResponse struct {
    Result string `json:"result"`
}
Step 2: Create the handler Create internal/your_feature/handler.go:
package your_feature

import (
    "net/http"
    "github.com/commenda/addresses-api/database"
    "github.com/commenda/addresses-api/internal/common/internal_context"
    "github.com/commenda/addresses-api/internal/your_feature/dto"
)

func YourHandler(c internal_context.HandlerContext, store database.Store) error {
    var req dto.YourRequest
    if err := c.Bind(&req); err != nil {
        return err
    }

    if err := c.Validate(&req); err != nil {
        return err
    }

    // Your business logic here
    result := processRequest(req)

    return c.JSON(http.StatusOK, dto.YourResponse{
        Result: result,
    })
}
Step 3: Add routes Create internal/your_feature/routes.go:
package your_feature

import (
    "github.com/commenda/addresses-api/internal/common/helpers"
    "github.com/labstack/echo/v4"
)

func AddRoutes(e *echo.Echo) {
    e.POST("/api/v1/your-endpoint", helpers.WrapHandler(YourHandler))
}
Step 4: Register routes in main server Edit cmd/serve.go:
import (
    "github.com/commenda/addresses-api/internal/your_feature"
)

func runServer(cmd *cobra.Command, args []string) {
    // ... existing code ...

    // Register routes
    e.GET("/", helpers.WrapHandler(index))
    e.GET("/healthz", helpers.WrapHandler(healthz))
    content_ingestion.AddRoutes(e)
    geoencode.AddRoutes(e)
    your_feature.AddRoutes(e)  // Add this line

    // ... rest of the code ...
}

5. Run tests again (they should pass)

go run . tests
Expected output:
PASS: TestYourNewFeature

6. Make SQL changes (if needed)

If your feature requires database changes, follow the Working with SQL guide: Step 1: Update schema Edit database/schema.sql:
CREATE TABLE "your_table" (
  "id" serial PRIMARY KEY,
  "name" text NOT NULL
);
Step 2: Write SQL queries Create internal/your_feature/sql/queries.sql:
-- name: GetYourData :one
SELECT * FROM your_table WHERE id = $1;

-- name: InsertYourData :one
INSERT INTO your_table (name)
VALUES ($1)
RETURNING *;
Step 3: Generate Go code
sqlc generate
Step 4: Generate migration
./scripts/generate.sh --name add_your_table
Step 5: Apply migration locally
./scripts/apply.sh

7. Create error handlers (if needed)

Create internal/your_feature/handler_errors.go:
package your_feature

import (
    "net/http"
    "github.com/commenda/addresses-api/internal/common/clients"
)

const (
    YOUR_CUSTOM_ERROR = "YOUR_CUSTOM_ERROR"
)

func YOUR_CUSTOM_ERROR_FUNC(path string) clients.ErrorWithStatusCode {
    statusCode := http.StatusBadRequest

    errorBody := &clients.Error_RFC9457{
        Type:    clients.CLIENT_INVALID_PARAMS,
        SubType: YOUR_CUSTOM_ERROR,
        Title:   "Your error title.",
        Detail: clients.Error_Description{
            Description: "Detailed error message.",
        },
        Status:   statusCode,
        Instance: path,
    }

    return clients.ErrorWithStatusCode{ErrorCode: statusCode, ErrorBody: *errorBody}
}
See Error handling for more details.

8. Commit your changes

Lefthook will automatically run pre-commit checks:
  • goimports: Formats your code
  • golangci-lint: Lints your code
If checks fail, fix the issues and commit again.
git add .
git commit -m "feat: add your feature description"

9. Push and create a pull request

git push origin feature/your-feature-name
Go to GitHub and create a pull request against main.

Pull request checks

When you create a PR, the following checks run automatically:
CheckDescriptionMust pass
Go LintCode quality and style checks✅ Yes
Go FormatCode formatting with goimports✅ Yes
Check BuildEnsures code compiles✅ Yes
Sqlc LintSQL query validation✅ Yes
Migrations LintDatabase migration validation✅ Yes
Integration TestsFull integration test suite✅ Yes

Common check failures

Migrations Lint fails

Error: “Missing schema migration” Fix: You modified database/schema.sql but didn’t create a migration:
./scripts/generate.sh --name your_change_description
git add database/migrations/
git commit -m "feat: add migration for your changes"
git push

Integration Tests fail

Error: Test failures in CI Fix: Run tests locally to debug:
go test -v ./tests/integration/...

Merging your PR

Once all checks pass and your PR is approved:
  1. Squash and merge (preferred) - Combines all commits into one
  2. Merge commit - Keeps all commits separate
  3. Rebase and merge - Replays commits on top of main
Note: Merging to main does NOT deploy anything. It only updates the main branch.

Deployment

After your PR is merged, you need to create a release to deploy:

Deploy to staging

  1. Go to ReleasesCreate a new release
  2. Tag: v0.1.0-rc.1 (increment version)
  3. Check: Set as a pre-release
  4. Click: Publish release
GitHub Actions will automatically:
  • Build Docker image
  • Push to ECR
  • Deploy to staging ECS
  • Run database migrations
  • Run smoke tests

Deploy to production

  1. Go to the staging release
  2. Click: Edit release
  3. Check: Set as the latest release
  4. Uncheck: Set as a pre-release
  5. Click: Update release
GitHub Actions will deploy to production.

Git hooks with Lefthook

Lefthook automatically runs checks before commits and pushes:

Pre-commit hooks

  • goimports-fix: Automatically formats staged Go files
  • goimports-check: Verifies all files are formatted
  • go-lint: Runs golangci-lint

Pre-push hooks

  • go-build: Ensures code compiles
If hooks fail, fix the issues before committing/pushing.

Best practices

Code style

  • Follow Go conventions (Lefthook enforces this)
  • Use meaningful variable names
  • Add comments for complex logic
  • Keep functions small and focused

Testing

  • Write tests before implementation (TDD)
  • Test both success and error cases
  • Use table-driven tests for multiple scenarios
  • Prioritize integration tests over unit tests

Database

  • Always create migrations for schema changes
  • Never modify existing migrations
  • Test migrations on a local database first
  • Use transactions for data consistency

Security

  • Validate all input
  • Use parameterized queries (sqlc does this automatically)
  • Never log sensitive data (passwords, API keys)
  • Follow principle of least privilege for API keys

Getting help

  • Questions: Ask in the team Slack channel
  • Bugs: Create a GitHub issue
  • Documentation: Check this documentation or the infrastructure README

Next steps