# Getting Started
This guide takes you from a running ArcadeDB server to your first successful reads and writes with Arex.
Arex stays small by design. You do not create a public client struct or open a session object in normal usage. Instead, you call module functions and pass options when you need to override defaults.
## Prerequisites
You need:
- an ArcadeDB server that is reachable over HTTP
- credentials for that server
- a target database, or permission to create one
If you want a predictable local environment, the Docker command in [README.md](../README.md) starts ArcadeDB with an empty `test_db` database and test credentials.
## Install The Library
Add Arex to your dependency list:
```elixir
defp deps do
[
{:arex, "~> 0.1.0"}
]
end
```
Then fetch dependencies:
```bash
mix deps.get
```
## Configure Connection Defaults
Arex resolves connection settings in this order:
1. per-call options
2. application config
3. environment variables for `url`, `user`, `pwd`, and `db`
`language` is resolved from call options or application config and otherwise defaults to `"sql"`.
Example `runtime.exs`:
```elixir
import Config
config :arex,
url: System.fetch_env!("ARCADEDB_URL"),
user: System.fetch_env!("ARCADEDB_USER"),
pwd: System.fetch_env!("ARCADEDB_PASSWORD"),
db: System.fetch_env!("ARCADEDB_DATABASE"),
language: "sql"
```
Environment fallback names:
- `AREX_URL`
- `AREX_USER`
- `AREX_PWD`
- `AREX_DB`
## Verify Connectivity
Before writing application code, confirm that Arex can reach the server:
```elixir
{:ok, :pong} = Arex.ping()
{:ok, info} = Arex.server_info()
{:ok, dbs} = Arex.Database.list()
```
If you need to target a specific server without changing app config, pass connection options directly:
```elixir
Arex.ping(
url: "http://localhost:2480/",
user: "test_user",
pwd: "test_password"
)
```
## Read Existing Data
When a database already contains data, `Arex.Query` is the fastest way to get started. With the empty local test database, create a type first and then query it:
```elixir
{:ok, _} = Arex.Schema.create_document_type("Beer", db: "test_db")
{:ok, _} = Arex.Record.persist(%{id: 1, name: "Hocus Pocus"}, db: "test_db", type: "Beer")
{:ok, rows} =
Arex.Query.sql(
"select from Beer where id = :id",
%{"id" => 1},
db: "test_db"
)
```
Helpful read helpers:
- `Arex.Query.sql/3` executes SQL and returns all rows.
- `Arex.Query.first/3` returns the first row or `nil`.
- `Arex.Query.one/3` returns exactly one row or fails with `:multiple_results`.
- `Arex.Query.page/3` returns a page map with `entries`, `limit`, `offset`, `count`, and `has_more?`.
- `Arex.Query.stream_pages/3` yields pages as a stream for larger result sets.
## Create Your Own Database And Type
If you are starting from an empty environment, provision a database and type first:
```elixir
{:ok, :created} = Arex.Database.create("crm")
{:ok, _} = Arex.Schema.create_document_type("Customer", db: "crm")
{:ok, _} = Arex.Schema.create_property("Customer", "external_id", :string, db: "crm")
{:ok, _} = Arex.Schema.create_index("Customer", ["external_id"], db: "crm", unique: true)
```
That gives you a concrete record type for the higher-level CRUD helpers.
## Write Your First Record
`Arex.Record.persist/2` inserts when no `@rid` is present and updates when `@rid` is present.
```elixir
{:ok, customer} =
Arex.Record.persist(
%{external_id: "cust-1", name: "Ada Lovelace"},
db: "crm",
type: "Customer",
tenant: "ankara",
scope: "sales"
)
```
Important rules:
- `type` is required for inserts unless the input map already contains `@type`
- `scope` always requires `tenant`
- insert-like helpers stamp `tenant` and `scope` into the record when present
- reads using the same boundary only see records that match that boundary
Fetch the record again by RID:
```elixir
{:ok, same_customer} =
Arex.Record.fetch(
customer["@rid"],
db: "crm",
tenant: "ankara",
scope: "sales"
)
```
## Understand The Boundary Model
Arex uses three layers of isolation:
1. `db` picks the ArcadeDB database.
2. `tenant` scopes records inside that database.
3. `scope` narrows records inside the tenant.
Boundary-aware helpers use these rules consistently:
- writes stamp `tenant` and `scope` into stored content
- reads automatically filter by those fields when provided
- attempting to cross boundaries yields `:not_found`
- helper APIs reject direct mutation of protected boundary keys
This is one of Arex's main advantages over scattering raw SQL through application code.
## Pick The Right Module
| Module | Start here when you need |
| ----------------- | ---------------------------------------- |
| `Arex` | connectivity checks and server metadata |
| `Arex.Query` | raw reads and paging |
| `Arex.Command` | raw write commands or SQLScript |
| `Arex.Record` | document-style CRUD |
| `Arex.Schema` | types, properties, indexes, and buckets |
| `Arex.Database` | create, drop, list, or inspect databases |
| `Arex.KV` | Redis-style key/value and hash commands |
| `Arex.TimeSeries` | TimeSeries DDL and endpoint wrappers |
| `Arex.Vector` | vector properties, indexes, and search |
| `Arex.Vertex` | graph vertex creation and traversal |
| `Arex.Edge` | graph edge creation and lookup |
## Specialized Models
Once the basic record flow is clear, the specialized modules follow the same
core Arex rules: option resolution, normalized `{:ok, value}` and
`{:error, error_map}` tuples, and explicit control over when you drop to raw
statements or raw HTTP.
### Key/Value
`Arex.KV` wraps ArcadeDB's Redis-language command surface with boundary-aware
key helpers.
```elixir
{:ok, "OK"} =
Arex.KV.set(
"session:ada",
"online",
db: "crm",
tenant: "ankara",
scope: "sales"
)
{:ok, "online"} =
Arex.KV.get(
"session:ada",
db: "crm",
tenant: "ankara",
scope: "sales"
)
```
Important behavior:
- wrapped key helpers namespace keys by `tenant` and `scope`
- raw `run/2` and `batch/2` stay caller-controlled
- `scope` still requires `tenant`
### Time-Series
`Arex.TimeSeries` covers type creation, helper-managed inserts, and dedicated
TimeSeries endpoints.
```elixir
{:ok, _} =
Arex.TimeSeries.create_type(
"CpuMetric",
"ts",
[{"host", :string}],
[{"value", :double}],
db: "metrics",
tenant: "ankara",
scope: "ops"
)
{:ok, _} =
Arex.TimeSeries.insert(
"CpuMetric",
%{"ts" => 1_715_000_001_000, "host" => "app-1", "value" => 0.42},
db: "metrics",
tenant: "ankara",
scope: "ops"
)
```
Important behavior:
- helper-managed writes stamp `tenant` and `scope` as tags when present
- wrapped SQL and latest-point reads apply those tags automatically
- raw SQL, raw PromQL, and raw payload helpers stay available when you need full control
### Vector Search
`Arex.Vector` is the convenience layer for ArcadeDB vector properties, indexes,
and nearest-neighbor queries.
```elixir
{:ok, _} = Arex.Schema.create_document_type("Doc", db: "search")
{:ok, _} = Arex.Vector.create_embedding_property("Doc", "embedding", db: "search")
{:ok, _} = Arex.Vector.create_dense_index("Doc", "embedding", 768, db: "search")
```
This module keeps the public API close to ArcadeDB's vector features while
avoiding repetitive metadata JSON and index SQL.
## What To Read Next
- [Records and Queries](records_and_queries.md) for the high-level CRUD API and paging semantics.
- [Graph and Schema](graph_and_schema.md) for schema changes, graph helpers, and provisioning workflows.
- [Runtime Behavior](runtime_behavior.md) for retries, timeouts, and normalized error handling.