Flowfull Elixir Client
View SourceA zero-dependency Elixir client library for Flowfull backends with built-in authentication, session management, and query building. Includes Phoenix integration for WebSockets and LiveView.
Professional, type-safe, and production-ready Elixir client for Flowfull API backends.
Features
- ✅ Minimal Dependencies - Only Req and Jason
- ✅ Type-Safe - Full typespec coverage
- ✅ Session Management - Auto-detection with pluggable storage
- ✅ Query Builder - Chainable API with 14+ filter operators
- ✅ Authentication - Complete auth system (25+ endpoints)
- ✅ Retry Logic - Configurable retries with exponential backoff
- ✅ Interceptors - Request/response middleware
- ✅ Phoenix Integration - WebSocket and LiveView support
- ✅ Multi-Tenant - Built-in multi-tenant support
- ✅ File Upload - Multipart form data support
Installation
Add flowfull to your list of dependencies in mix.exs:
def deps do
[
{:flowfull, "~> 0.1.0"}
]
endQuick Start
# Create a client
client = Flowfull.new("https://api.example.com")
# Make a simple GET request
{:ok, response} = Flowfull.get(client, "/users")
# Use query builder
{:ok, users} =
Flowfull.query(client, "/users")
|> Flowfull.Query.where("age", Flowfull.Operators.gte(18))
|> Flowfull.Query.where("status", Flowfull.Operators.eq("active"))
|> Flowfull.Query.sort("created_at", :desc)
|> Flowfull.Query.page(1)
|> Flowfull.Query.limit(10)
|> Flowfull.Query.execute()
# Authentication
{:ok, result} = Flowfull.Auth.login(client, %{
email: "user@example.com",
password: "password123"
})
# Get current user
{:ok, user} = Flowfull.Auth.me(client)Configuration
# With options
client = Flowfull.new("https://api.example.com",
session_id: "abc123",
timeout: 60_000,
headers: %{"X-Custom" => "value"},
retry_attempts: 5,
retry_exponential: true,
storage: Flowfull.Storage.File.new(".sessions")
)
# With session function
client = Flowfull.new("https://api.example.com",
get_session_id: fn ->
{:ok, System.get_env("SESSION_ID")}
end
)
# Include session in all requests
client = Flowfull.new("https://api.example.com",
include_session: true,
session_header: "X-Session-Id"
)HTTP Methods
# GET
{:ok, response} = Flowfull.get(client, "/users")
# POST
{:ok, response} = Flowfull.post(client, "/users", %{
name: "John Doe",
email: "john@example.com"
})
# PUT
{:ok, response} = Flowfull.put(client, "/users/123", %{
name: "Jane Doe"
})
# PATCH
{:ok, response} = Flowfull.patch(client, "/users/123", %{
email: "jane@example.com"
})
# DELETE
{:ok, response} = Flowfull.delete(client, "/users/123")Query Builder
# Complex query with multiple filters
{:ok, products} =
Flowfull.query(client, "/products")
|> Flowfull.Query.where("price", Flowfull.Operators.between(10, 100))
|> Flowfull.Query.where("category", Flowfull.Operators.in_list(["electronics", "books"]))
|> Flowfull.Query.where("name", Flowfull.Operators.ilike("%laptop%"))
|> Flowfull.Query.where("stock", Flowfull.Operators.gt(0))
|> Flowfull.Query.sort("price", :asc)
|> Flowfull.Query.sort("created_at", :desc)
|> Flowfull.Query.page(1)
|> Flowfull.Query.limit(20)
|> Flowfull.Query.select(["id", "name", "price"])
|> Flowfull.Query.execute()Filter Operators
| Operator | Function | Example | SQL Equivalent |
|---|---|---|---|
| Equality | eq(value) | where("age", eq(25)) | age = 25 |
| Not Equal | ne(value) | where("status", ne("inactive")) | status != 'inactive' |
| Greater Than | gt(value) | where("price", gt(100)) | price > 100 |
| Greater or Equal | gte(value) | where("age", gte(18)) | age >= 18 |
| Less Than | lt(value) | where("stock", lt(10)) | stock < 10 |
| Less or Equal | lte(value) | where("price", lte(50)) | price <= 50 |
| LIKE | like(pattern) | where("name", like("%John%")) | name LIKE '%John%' |
| ILIKE | ilike(pattern) | where("email", ilike("%@gmail.com")) | email ILIKE '%@gmail.com' |
| Contains | contains(value) | where("description", contains("laptop")) | description LIKE '%laptop%' |
| Starts With | starts_with(prefix) | where("name", starts_with("A")) | name LIKE 'A%' |
| Ends With | ends_with(suffix) | where("email", ends_with("@example.com")) | email LIKE '%@example.com' |
| IN | in_list(values) | where("status", in_list(["active", "pending"])) | status IN ('active', 'pending') |
| NOT IN | not_in(values) | where("role", not_in(["admin", "super"])) | role NOT IN ('admin', 'super') |
| IS NULL | is_null() | where("deleted_at", is_null()) | deleted_at IS NULL |
| NOT NULL | not_null() | where("email", not_null()) | email IS NOT NULL |
| BETWEEN | between(min, max) | where("age", between(18, 65)) | age BETWEEN 18 AND 65 |
| NOT BETWEEN | not_between(min, max) | where("price", not_between(0, 10)) | price NOT BETWEEN 0 AND 10 |
Authentication
Login & Registration
# Login with email
{:ok, result} = Flowfull.Auth.login(client, %{
email: "user@example.com",
password: "password123"
})
# Login with username
{:ok, result} = Flowfull.Auth.login(client, %{
user_name: "johndoe",
password: "password123"
})
# Register new user
{:ok, result} = Flowfull.Auth.register(client, %{
email: "user@example.com",
password: "password123",
name: "John",
last_name: "Doe"
})
# Logout
{:ok, _} = Flowfull.Auth.logout(client)
# Get current user
{:ok, user} = Flowfull.Auth.me(client)
# Update profile
{:ok, user} = Flowfull.Auth.update_profile(client, %{
name: "Jane",
phone: "+1234567890"
})Password Reset
# Request password reset
{:ok, _} = Flowfull.Auth.Password.request_reset(client, %{
email: "user@example.com",
reset_url: "https://app.example.com/reset-password"
})
# Validate reset token
{:ok, result} = Flowfull.Auth.Password.validate_token(client, token)
# Complete password reset
{:ok, _} = Flowfull.Auth.Password.complete_reset(client, %{
token: token,
password: "newpassword123"
})
# Change password (authenticated)
{:ok, _} = Flowfull.Auth.Password.change_password(client, %{
old_password: "oldpassword",
new_password: "newpassword"
})Token Authentication
# Create login token
{:ok, result} = Flowfull.Auth.Token.create(client, %{
email: "user@example.com",
token_url: "https://app.example.com/login"
})
# Validate token
{:ok, result} = Flowfull.Auth.Token.validate(client, token)
# Login with token
{:ok, result} = Flowfull.Auth.Token.login(client, token)Social OAuth
# Get available providers
{:ok, providers} = Flowfull.Auth.Social.get_providers(client)
# Start OAuth flow
{:ok, auth_url} = Flowfull.Auth.Social.start_oauth(
client,
:google,
"https://app.example.com/callback"
)
# Login with OAuth (after callback)
{:ok, result} = Flowfull.Auth.Social.login_api(client, %{
provider: "google",
code: oauth_code
})
# Get linked accounts
{:ok, accounts} = Flowfull.Auth.Social.get_accounts(client)
# Unlink account
{:ok, _} = Flowfull.Auth.Social.unlink(client, :google)Phoenix Integration
WebSocket Authentication
# In your UserSocket module
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
def connect(%{"session_id" => session_id}, socket, _connect_info) do
client = Flowfull.new(Application.get_env(:my_app, :flowfull_url))
case Flowfull.Phoenix.validate_socket_session(client, session_id) do
{:ok, user} ->
{:ok, assign(socket, :user, user)}
{:error, _reason} ->
:error
end
end
def connect(_params, _socket, _connect_info), do: :error
endLiveView Authentication
# In your LiveView module
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
def mount(_params, %{"session_id" => session_id}, socket) do
client = Flowfull.new(Application.get_env(:my_app, :flowfull_url))
case Flowfull.Phoenix.validate_liveview_session(client, session_id) do
{:ok, user} ->
{:ok, assign(socket, :current_user, user)}
{:error, _reason} ->
{:ok, redirect(socket, to: "/login")}
end
end
endController Authentication Plug
# In your router
pipeline :authenticated do
plug Flowfull.Phoenix.SessionPlug,
client: fn -> Flowfull.new(Application.get_env(:my_app, :flowfull_url)) end
end
scope "/dashboard", MyAppWeb do
pipe_through [:browser, :authenticated]
get "/", DashboardController, :index
endStorage Adapters
Memory Storage (Agent-based)
# Start the memory storage
{:ok, _} = Flowfull.Storage.Memory.start_link()
# Use with client
client = Flowfull.new("https://api.example.com",
storage: Flowfull.Storage.Memory
)File Storage
# Use file storage
client = Flowfull.new("https://api.example.com",
storage: Flowfull.Storage.File.new(".sessions")
)ETS Storage (High Performance)
# Initialize ETS storage
Flowfull.Storage.ETS.init()
# Use with client
client = Flowfull.new("https://api.example.com",
storage: Flowfull.Storage.ETS
)Interceptors
# Add request interceptor
client = Flowfull.add_request_interceptor(client, fn request ->
IO.inspect(request, label: "Request")
{:ok, request}
end)
# Add response interceptor
client = Flowfull.add_response_interceptor(client, fn response ->
IO.inspect(response, label: "Response")
{:ok, response}
end)Examples
See the examples directory for complete examples:
- Basic Usage - Simple HTTP requests
- Query Builder - Advanced queries
- Authentication - Complete auth flow
- Phoenix WebSocket - WebSocket integration
- Phoenix LiveView - LiveView integration
Documentation
- Quick Start Guide - Get started in 5 minutes
- API Documentation - Complete API reference
- Phoenix Integration Guide - Phoenix-specific features
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
Flowfull Elixir Client is licensed under the AGPL-3.0-only license.
What does this mean?
- ✅ Free to use - Use commercially without paying anything
- ✅ Modify freely - Change the code as needed
- ✅ Distribute - Share with others
- ⚠️ Share modifications - If you modify and offer as a web service, you must release your changes
- ⚠️ Same license - Derivative works must use AGPL-3.0
Commercial License Available
For organizations that cannot comply with AGPL-3.0 or need:
- 💼 Keep modifications private
- 🛡️ Legal indemnification
- 🎯 Enterprise support and SLA
- 🚀 Custom features
Contact: enterprise@pubflow.com Learn more: https://pubflow.com/dual-licensing
See LICENSE for full details.
Support
Copyright © 2024-present Pubflow, Inc. SPDX-License-Identifier: AGPL-3.0-only
Made with ❤️ by the Pubflow team