Your First Reactor

View Source

Welcome to Reactor! In this tutorial, you'll build your first reactor step by step, learning the core concepts through hands-on practice.

What you'll build

A user registration workflow that validates email, hashes passwords, and creates user records.

You'll learn

  • Defining reactor inputs and steps
  • Creating dependencies between steps
  • Running reactors and handling results

Workflow Overview

Here's what we'll build - a simple user registration workflow:

graph TB
    A[input: email] --> B[validate_email]
    A2[input: password] --> C[hash_password]
    B --> D[create_user]
    C --> D
    D --> E[Registration Complete]
    
    style A fill:#e1f5fe
    style A2 fill:#e1f5fe
    style E fill:#c8e6c9
    style D fill:#fff3e0

Prerequisites

  • Basic Elixir knowledge
  • Igniter New installed: mix archive.install hex igniter_new

Step 1: Create a new project with Reactor

Let's start by creating a new project with Reactor already installed and configured:

mix igniter.new reactor_tutorial --install reactor
cd reactor_tutorial

This creates a new Elixir project and automatically:

  • Adds Reactor to your dependencies
  • Installs the dependencies
  • Sets up any necessary configuration

Step 2: Your first reactor

Create a new file lib/user_registration.ex and define your first reactor:

defmodule UserRegistration do
  use Reactor

  # Define what inputs this reactor expects
  input :email
  input :password

  # Define a simple step that validates email
  step :validate_email do
    argument :email, input(:email)
    
    run fn %{email: email}, _context ->
      if String.contains?(email, "@") do
        {:ok, email}
      else
        {:error, "Email must contain @"}
      end
    end
  end

  # Define a step that hashes the password
  step :hash_password do
    argument :password, input(:password)
    
    run fn %{password: password}, _context ->
      hashed = :crypto.hash(:sha256, password) |> Base.encode16()
      {:ok, hashed}
    end
  end

  # Define a step that creates the user
  step :create_user do
    argument :email, result(:validate_email)
    argument :password_hash, result(:hash_password)
    
    run fn %{email: email, password_hash: password_hash}, _context ->
      user = %{
        id: :rand.uniform(10000),
        email: email,
        password_hash: password_hash,
        created_at: DateTime.utc_now()
      }
      {:ok, user}
    end
  end

  # Specify what to return when the reactor completes
  return :create_user
end

Step 3: Understanding your reactor

Before running it, let's understand what you've built:

Inputs define the parameters your reactor accepts, like function arguments.

Steps are units of work with three key parts:

  • Arguments specify what data the step needs and where it comes from
  • Run function performs the actual work
  • Dependencies are created automatically based on arguments

Notice how :create_user depends on results from both :validate_email and :hash_password. This creates an execution order where the first two steps can run concurrently, then :create_user runs after both complete.

Return specifies what value the reactor should return when everything succeeds.

Step 4: Run your reactor

Let's test your reactor! Start an IEx session:

iex -S mix

Now run your reactor:

# Test with valid data
{:ok, user} = Reactor.run(UserRegistration, %{
  email: "alice@example.com",
  password: "secret123"
})

IO.inspect(user)
# Should output something like:
# %{
#   id: 1234,
#   email: "alice@example.com", 
#   password_hash: "2BB80D537B1DA3E38BD30361AA855686BDE0EACD7162FEF6A25FE97BF527A25B",
#   created_at: ~U[2024-01-15 10:30:00.123456Z]
# }

Try with invalid data:

# Test with invalid email
{:error, reason} = Reactor.run(UserRegistration, %{
  email: "not-an-email",
  password: "secret123" 
})

IO.inspect(reason)
# Should output: "Email must contain @"

Step 5: Execution order

Here's something important: Reactor doesn't execute steps in the order you wrote them. Instead, it builds a dependency graph and runs steps as soon as their dependencies are available.

In your reactor:

  1. :validate_email and :hash_password can run simultaneously because they only depend on inputs
  2. :create_user runs after both previous steps complete because it needs their results

This automatic parallelization is one of Reactor's key features - you get concurrent execution without having to think about it!

What you learned

You've built your first reactor and learned the fundamentals:

  • Inputs define reactor parameters
  • Steps contain arguments, run functions, and automatic dependencies
  • Arguments create execution order by specifying data sources
  • Concurrent execution happens automatically when steps don't depend on each other
  • Dependencies determine when steps run, not the order you write them

What's next

Now that you understand the basics, you're ready to learn more advanced concepts:

Or explore specific use cases in our How-to Guides section.

For quick syntax reference, see the Reactor Cheatsheet.

Happy building with Reactor! 🚀