Getting Started with ReqFly

View Source

This guide will walk you through installing and using ReqFly to interact with the Fly.io Machines API.

Prerequisites

Before you begin, make sure you have:

Installation

Add ReqFly to your project's dependencies in mix.exs:

def deps do
  [
    {:req_fly, "~> 0.1.0"}
  ]
end

Then fetch the dependencies:

mix deps.get

Getting Your API Token

To use the Fly.io API, you need a personal access token:

  1. Visit https://fly.io/user/personal_access_tokens
  2. Click "Create Token"
  3. Give it a descriptive name (e.g., "ReqFly Development")
  4. Copy the token - you won't be able to see it again!

Storing Your Token

For development, you can use environment variables:

# Add to your .env or .envrc file
export FLY_API_TOKEN="your_token_here"

Important: Never commit your API token to version control!

Your First Request

Let's start with a simple example that lists your Fly.io apps:

# In iex
iex> req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))
iex> {:ok, apps} = ReqFly.Apps.list(req, org_slug: "personal")
{:ok, [%{"name" => "my-app", "status" => "running", ...}]}

Congratulations! You've just made your first API call with ReqFly.

Common Workflows

Creating and Managing Apps

# Create a Req client
req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

# Create a new app
{:ok, app} = ReqFly.Apps.create(req,
  app_name: "my-test-app",
  org_slug: "personal"
)

# Get app details
{:ok, app_details} = ReqFly.Apps.get(req, "my-test-app")
IO.inspect(app_details, label: "App Details")

# List all apps
{:ok, apps} = ReqFly.Apps.list(req, org_slug: "personal")
Enum.each(apps, fn app ->
  IO.puts("App: #{app["name"]} - Status: #{app["status"]}")
end)

Creating and Managing Machines

req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

# Define machine configuration
config = %{
  image: "flyio/hellofly:latest",
  env: %{
    "PORT" => "8080"
  },
  guest: %{
    cpus: 1,
    memory_mb: 256
  }
}

# Create a machine
{:ok, machine} = ReqFly.Machines.create(req,
  app_name: "my-test-app",
  config: config,
  region: "ewr"
)

machine_id = machine["id"]
IO.puts("Created machine: #{machine_id}")

# Start the machine
{:ok, _} = ReqFly.Machines.start(req,
  app_name: "my-test-app",
  machine_id: machine_id
)

# Wait for it to be ready
{:ok, ready_machine} = ReqFly.Machines.wait(req,
  app_name: "my-test-app",
  machine_id: machine_id,
  instance_id: machine["instance_id"],
  state: "started",
  timeout: 60
)

IO.puts("Machine is ready!")

Using the Orchestrator

The Orchestrator module provides high-level workflows that combine multiple operations:

req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

# Create app and wait for it to be active
{:ok, app} = ReqFly.Orchestrator.create_app_and_wait(req,
  app_name: "orchestrated-app",
  org_slug: "personal",
  timeout: 120
)

# Create machine and wait for it to start
config = %{
  image: "nginx:latest",
  guest: %{cpus: 1, memory_mb: 256}
}

{:ok, machine} = ReqFly.Orchestrator.create_machine_and_wait(req,
  app_name: "orchestrated-app",
  config: config,
  region: "ewr",
  timeout: 60
)

IO.puts("Everything is ready!")

Managing Secrets

req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

# Set an environment variable secret
{:ok, _} = ReqFly.Secrets.create(req,
  app_name: "my-app",
  label: "DATABASE_URL",
  type: "env",
  value: "postgres://localhost/mydb"
)

# Generate a random secret
{:ok, secret} = ReqFly.Secrets.generate(req,
  app_name: "my-app",
  label: "SECRET_KEY_BASE",
  type: "env"
)

IO.puts("Generated secret value: #{secret["value"]}")

# List all secrets
{:ok, secrets} = ReqFly.Secrets.list(req, app_name: "my-app")

Working with Volumes

req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

# Create a persistent volume
{:ok, volume} = ReqFly.Volumes.create(req,
  app_name: "my-app",
  name: "postgres_data",
  region: "ewr",
  size_gb: 10
)

# Extend the volume
{:ok, _} = ReqFly.Volumes.extend(req,
  app_name: "my-app",
  volume_id: volume["id"],
  size_gb: 20
)

# Create a snapshot for backup
{:ok, snapshot} = ReqFly.Volumes.create_snapshot(req,
  app_name: "my-app",
  volume_id: volume["id"]
)

Error Handling

ReqFly uses pattern matching for error handling:

req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

case ReqFly.Apps.get(req, "nonexistent-app") do
  {:ok, app} ->
    IO.puts("Found app: #{app["name"]}")
    
  {:error, %ReqFly.Error{status: 404, message: message}} ->
    IO.puts("App not found: #{message}")
    
  {:error, %ReqFly.Error{status: 401}} ->
    IO.puts("Authentication failed - check your API token")
    
  {:error, %ReqFly.Error{status: status, message: message}} ->
    IO.puts("API error #{status}: #{message}")
end

Configuration Options

ReqFly supports several configuration patterns:

req = Req.new() |> ReqFly.attach(token: "your_token")

2. Environment Variable

req = Req.new() |> ReqFly.attach(token: System.get_env("FLY_API_TOKEN"))

3. Application Configuration

# config/config.exs
config :req_fly, token: "your_token"

# In your code
req = Req.new() |> ReqFly.attach()
# config/runtime.exs
config :req_fly, token: System.get_env("FLY_API_TOKEN")

# In your code
req = Req.new() |> ReqFly.attach()

Advanced Options

req = Req.new() |> ReqFly.attach(
  token: System.get_env("FLY_API_TOKEN"),
  base_url: "https://api.machines.dev/v1",  # Custom API endpoint
  retry: :safe_transient,                    # Retry safe methods
  max_retries: 5,                            # More aggressive retries
  telemetry_prefix: [:my_app, :fly]         # Custom telemetry prefix
)

Troubleshooting

Authentication Errors

If you get a 401 error:

  1. Check that your token is correct
  2. Ensure the token hasn't expired
  3. Verify you're using the correct token format (starts with fly_)

Rate Limiting

The Fly.io API has rate limits. ReqFly automatically retries with exponential backoff, but if you're making many requests, consider:

  1. Adding delays between requests
  2. Batching operations where possible
  3. Using the Orchestrator for complex workflows

Timeout Errors

For long-running operations:

# Increase timeout for operations that might take longer
{:ok, machine} = ReqFly.Machines.wait(req,
  app_name: "my-app",
  machine_id: machine_id,
  instance_id: instance_id,
  state: "started",
  timeout: 300  # 5 minutes
)

Next Steps

Now that you're familiar with the basics, you can:

  1. Explore the API Documentation - Visit https://hexdocs.pm/req_fly for detailed module documentation
  2. Read the Fly.io API Docs - Learn more at https://fly.io/docs/machines/api/
  3. Check Out Examples - See the README for more comprehensive examples
  4. Build Something! - Start deploying and managing your applications with ReqFly

Getting Help

Happy coding! 🚀