Connection Management

arango‑typed provides a Mongoose‑like connect() and a connection cache that reuses arangojs Database instances for identical connection parameters. It can also auto‑create the target database if it doesn't exist.

What is Connection Management?

Connection management in arango-typed handles establishing, caching, and reusing database connections to ArangoDB. It provides a simple, Mongoose-like API while optimizing performance through connection reuse and automatic database creation.

Why Use arango-typed Connection Management?

  • Performance: Connection caching eliminates overhead from repeated connections
  • Simplicity: Mongoose-like API that's familiar and easy to use
  • Auto-creation: Automatically creates databases if they don't exist (configurable)
  • Helper Functions: Convenient access to database, graph manager, vector search, and more
  • Error Handling: Descriptive error messages with automatic retry on database creation
  • Cluster Support: Built-in support for ArangoDB clusters with multiple coordinators

Connection Formats

arango-typed supports multiple connection formats for flexibility:

1. URI Format (Mongoose-like)

The simplest format, similar to Mongoose's connection string:

import { connect } from 'arango-typed';

// Format: http://host:port/database
await connect('http://localhost:8529/myapp', {
  username: 'root',
  password: ''
});

// With authentication
await connect('http://localhost:8529/production', {
  username: 'admin',
  password: 'secret123'
});

2. Simplified Object Format

Clean, readable object format with flat properties:

await connect({
  url: 'http://localhost:8529',
  database: 'myapp',
  username: 'root',
  password: ''
});

// All options
await connect({
  url: 'http://localhost:8529',
  database: 'myapp',
  username: 'root',
  password: '',
  autoCreateDatabase: true,
  arangoVersion: 30800
});

3. Full Object Format (Backward Compatible)

Full format with nested auth object for compatibility:

await connect({
  url: 'http://localhost:8529',
  databaseName: 'myapp',  // Can also use 'database'
  auth: {
    username: 'root',
    password: ''
  },
  autoCreateDatabase: true
});

Auto-Create Database

By default, arango-typed automatically creates the database if it doesn't exist. This is perfect for development but should be disabled in production for safety.

How It Works

  1. Attempts to connect to the specified database
  2. If database not found, connects to _system database
  3. Creates the target database using createDatabase()
  4. Reconnects to the newly created database
  5. Caches the connection for future use

Enable Auto-Create (Default)

// Auto-create enabled by default
await connect({
  url: 'http://localhost:8529',
  database: 'appdb',
  username: 'root',
  password: ''
  // autoCreateDatabase: true (default)
});

Disable Auto-Create (Production)

// Disable for production safety
await connect({
  url: 'http://localhost:8529',
  database: 'production',
  username: 'admin',
  password: process.env.ARANGO_PASSWORD,
  autoCreateDatabase: false  // Explicitly disable
});

Environment-Based Auto-Create

await connect({
  url: process.env.ARANGO_URL,
  database: process.env.ARANGO_DB,
  username: process.env.ARANGO_USER,
  password: process.env.ARANGO_PASS,
  autoCreateDatabase: process.env.NODE_ENV !== 'production'  // Only in dev/test
});

Connection Caching

arango-typed automatically caches connections to improve performance. Connections are cached by a key composed of url|database|username.

How Caching Works

  1. First Connection: Creates new Database instance and caches it
  2. Subsequent Calls: Checks cache for matching connection parameters
  3. Health Check: Performs lightweight version() check to verify connection is still valid
  4. Reuse: Returns cached connection if valid, otherwise creates new one

Example: Connection Reuse

// First call - creates and caches connection
await connect('http://localhost:8529/appdb', { username: 'root', password: '' });

// Second call - reuses cached connection (fast!)
await connect('http://localhost:8529/appdb', { username: 'root', password: '' });

// Different database - creates new connection
await connect('http://localhost:8529/otherdb', { username: 'root', password: '' });

// Different user - creates new connection
await connect('http://localhost:8529/appdb', { username: 'admin', password: '' });

Cache Key Generation

The cache key is generated from: url|database|username

  • Same URL, database, and username = same cache entry
  • Different password doesn't affect cache (authentication happens per request)
  • Cluster URLs use the first coordinator URL for cache key

Helper Functions

After connecting, use helper functions to access various services bound to the default connection:

getDatabase()

Get the arangojs Database instance for direct AQL queries:

import { connect, getDatabase } from 'arango-typed';

await connect('http://localhost:8529/myapp', { username: 'root', password: '' });

const db = getDatabase();

// Use arangojs directly
const cursor = await db.query('FOR doc IN users RETURN doc');
const users = await cursor.all();

getVectorSearch()

Get VectorSearch instance for similarity search:

import { getVectorSearch } from 'arango-typed';

const vectorSearch = getVectorSearch();
const results = await vectorSearch.similaritySearch('documents', queryVector, { limit: 10 });

getGraphManager()

Get GraphManager instance for graph operations:

import { getGraphManager } from 'arango-typed';

const graphManager = getGraphManager();
const graph = await graphManager.createGraph('social', {
  vertices: ['users', 'posts'],
  edges: ['follows', 'likes']
});

getTransactionManager()

Get TransactionManager instance for ACID transactions:

import { getTransactionManager } from 'arango-typed';

const txManager = getTransactionManager();
const result = await txManager.execute(async (trx) => {
  // Transaction operations
  await trx.collection('users').save({ name: 'John' });
  await trx.collection('posts').save({ title: 'Hello' });
  return { success: true };
});

getSearchManager()

Get SearchManager instance for full-text search:

import { getSearchManager } from 'arango-typed';

const searchManager = getSearchManager();
const results = await searchManager.search('documents', 'search query', {
  limit: 10
});

getGeoQuery()

Get GeoQuery instance for geospatial queries:

import { getGeoQuery } from 'arango-typed';

const geoQuery = getGeoQuery();
const nearby = await geoQuery.near('locations', {
  latitude: 40.7128,
  longitude: -74.0060,
  radius: 1000  // meters
});

getBulkOperations()

Get BulkOperations instance for batch operations:

import { getBulkOperations } from 'arango-typed';

const bulkOps = getBulkOperations();
await bulkOps.import('users', [
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' }
]);

Connection Options

Complete list of connection options with detailed explanations:

url (string | string[])

ArangoDB server URL(s). For clusters, provide an array of coordinator URLs.

// Single server
url: 'http://localhost:8529'

// HTTPS
url: 'https://arangodb.example.com:8529'

// Cluster (array of coordinators)
url: [
  'http://coordinator1:8529',
  'http://coordinator2:8529',
  'http://coordinator3:8529'
]

database / databaseName (string)

Database name. Both database and databaseName are supported for compatibility.

database: 'myapp'
// or
databaseName: 'myapp'

username / password (string)

Authentication credentials. Can be provided as flat properties or nested in auth object.

// Flat format
username: 'root',
password: 'secret'

// Nested format
auth: {
  username: 'root',
  password: 'secret'
}

agent (any)

HTTP agent for custom connection settings (keep-alive, proxies, TLS).

import { Agent } from 'https';

const agent = new Agent({
  keepAlive: true,
  maxSockets: 50
});

await connect({
  url: 'https://arangodb.example.com:8529',
  database: 'myapp',
  username: 'admin',
  password: 'secret',
  agent: agent
});

arangoVersion (number)

Optional ArangoDB version hint for arangojs compatibility (e.g., 30800 for 3.8.0).

arangoVersion: 30800  // 3.8.0
arangoVersion: 31100  // 3.11.0

autoCreateDatabase (boolean)

Whether to automatically create the database if it doesn't exist. Default: true.

autoCreateDatabase: true   // Enable (default)
autoCreateDatabase: false  // Disable (production)

Cluster Connections

For ArangoDB clusters, provide multiple coordinator URLs. arango-typed will automatically handle failover and load balancing.

Basic Cluster Connection

await connect({
  url: [
    'http://coordinator1:8529',
    'http://coordinator2:8529',
    'http://coordinator3:8529'
  ],
  database: 'myapp',
  username: 'root',
  password: 'secret'
});

Cluster with HTTPS

await connect({
  url: [
    'https://coordinator1.example.com:8529',
    'https://coordinator2.example.com:8529',
    'https://coordinator3.example.com:8529'
  ],
  database: 'production',
  username: 'admin',
  password: process.env.ARANGO_PASSWORD
});

Environment-Based Configuration

Use environment variables for different environments (development, staging, production):

Basic Environment Setup

import { connect } from 'arango-typed';
import dotenv from 'dotenv';

dotenv.config();

await connect({
  url: process.env.ARANGO_URL || 'http://localhost:8529',
  database: process.env.ARANGO_DB || 'myapp',
  username: process.env.ARANGO_USER || 'root',
  password: process.env.ARANGO_PASS || '',
  autoCreateDatabase: process.env.NODE_ENV !== 'production'
});

Production Configuration

// .env.production
// ARANGO_URL=https://arangodb.example.com:8529
// ARANGO_DB=production
// ARANGO_USER=admin
// ARANGO_PASS=secure_password_here

await connect({
  url: process.env.ARANGO_URL!,
  database: process.env.ARANGO_DB!,
  username: process.env.ARANGO_USER!,
  password: process.env.ARANGO_PASS!,
  autoCreateDatabase: false  // Never auto-create in production
});

Configuration Module Pattern

// config/db.ts
import { connect, getDatabase } from 'arango-typed';

export async function connectDB() {
  try {
    await connect({
      url: process.env.ARANGO_URL || 'http://localhost:8529',
      database: process.env.ARANGO_DB || 'myapp',
      username: process.env.ARANGO_USER || 'root',
      password: process.env.ARANGO_PASS || '',
      autoCreateDatabase: process.env.NODE_ENV !== 'production'
    });
    
    const db = getDatabase();
    console.log('✅ ArangoDB connected');
    return db;
  } catch (error) {
    console.error('❌ ArangoDB connection failed:', error);
    throw error;
  }
}

// app.ts
import { connectDB } from './config/db';

await connectDB();

Error Handling

Connection errors throw ConnectionError with descriptive messages. Always wrap connect() in try-catch for proper error handling.

Basic Error Handling

import { connect, ConnectionError } from 'arango-typed';

try {
  await connect({
    url: 'http://localhost:8529',
    database: 'myapp',
    username: 'root',
    password: ''
  });
  console.log('Connected successfully');
} catch (error) {
  if (error instanceof ConnectionError) {
    console.error('Connection error:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
  process.exit(1);
}

Error with Auto-Create

If auto-create fails, the error message includes both the original error and the creation error:

try {
  await connect({
    url: 'http://localhost:8529',
    database: 'myapp',
    username: 'root',
    password: '',
    autoCreateDatabase: true
  });
} catch (error) {
  // Error message includes: "Failed to connect to ArangoDB: [original error] (auto-create failed: [creation error])"
  console.error(error.message);
}

Retry Logic

async function connectWithRetry(maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await connect({
        url: process.env.ARANGO_URL!,
        database: process.env.ARANGO_DB!,
        username: process.env.ARANGO_USER!,
        password: process.env.ARANGO_PASS!
      });
      return;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      console.log(`Retry ${i + 1}/${maxRetries}...`);
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Connection Lifecycle

Understanding how connections are managed throughout your application:

1. First Connection

  1. Validates connection parameters
  2. Creates new arangojs Database instance
  3. Attempts to connect to specified database
  4. If database not found and auto-create enabled, creates database
  5. Performs health check (version())
  6. Caches connection for reuse

2. Subsequent Connections

  1. Checks cache for matching connection parameters
  2. If found, performs lightweight health check
  3. Returns cached connection if valid
  4. Creates new connection if cache miss or invalid

3. Health Checks

On connection reuse, a lightweight version() check verifies the connection is still valid. If the check fails, the cache entry is removed and a new connection is created.

4. Error Recovery

If a connection fails, the cache entry is automatically cleared, allowing retry logic to create a fresh connection.

Complete Application Setup

Full example of connection setup in a production application:

// config/db.ts
import { connect, getDatabase, ConnectionError } from 'arango-typed';
import dotenv from 'dotenv';

dotenv.config();

let isConnected = false;

export async function initializeDatabase() {
  if (isConnected) {
    return getDatabase();
  }

  try {
    const db = await connect({
      url: process.env.ARANGO_URL || 'http://localhost:8529',
      database: process.env.ARANGO_DB || 'myapp',
      username: process.env.ARANGO_USER || 'root',
      password: process.env.ARANGO_PASS || '',
      autoCreateDatabase: process.env.NODE_ENV !== 'production',
      arangoVersion: process.env.ARANGO_VERSION ? parseInt(process.env.ARANGO_VERSION) : undefined
    });

    isConnected = true;
    console.log(`✅ Connected to ArangoDB: ${process.env.ARANGO_DB}`);
    
    // Verify connection
    const version = await db.version();
    console.log(`📊 ArangoDB version: ${version.version}`);
    
    return db;
  } catch (error) {
    if (error instanceof ConnectionError) {
      console.error('❌ Connection error:', error.message);
    } else {
      console.error('❌ Unexpected error:', error);
    }
    throw error;
  }
}

// Graceful shutdown
export async function closeDatabase() {
  // arangojs handles connection cleanup automatically
  // No explicit close needed, but you can clear cache if needed
  isConnected = false;
}

// app.ts
import { initializeDatabase } from './config/db';

async function startApp() {
  try {
    await initializeDatabase();
    // Start your application
  } catch (error) {
    console.error('Failed to start application:', error);
    process.exit(1);
  }
}

startApp();

Advanced Topics

Custom HTTP Agent

Use custom HTTP agent for proxies, TLS settings, or connection pooling:

import { Agent } from 'https';
import { connect } from 'arango-typed';

const agent = new Agent({
  keepAlive: true,
  maxSockets: 50,
  rejectUnauthorized: false  // For self-signed certificates
});

await connect({
  url: 'https://arangodb.example.com:8529',
  database: 'myapp',
  username: 'admin',
  password: 'secret',
  agent: agent
});

Connection State Checking

import { getConnection, getDatabase } from 'arango-typed';

// Check if connected
const connection = getConnection();
if (connection.isConnected()) {
  const db = getDatabase();
  // Use database
} else {
  // Reconnect
  await connect({ /* ... */ });
}

Multiple Database Connections

While arango-typed uses a default connection, you can manage multiple connections:

import { Connection } from 'arango-typed';

// Create separate connection instances
const conn1 = new Connection({
  url: 'http://localhost:8529',
  databaseName: 'db1',
  auth: { username: 'root', password: '' }
});

const conn2 = new Connection({
  url: 'http://localhost:8529',
  databaseName: 'db2',
  auth: { username: 'root', password: '' }
});

await conn1.connect();
await conn2.connect();

const db1 = conn1.getDatabase();
const db2 = conn2.getDatabase();

Best Practices

  • Connect Once: Connect once at application startup, reuse throughout. Don't call connect() in every request.
  • Environment Variables: Never hardcode credentials. Use environment variables or secrets management (AWS Secrets Manager, HashiCorp Vault, etc.).
  • Auto-create in Development Only: Enable autoCreateDatabase in development/test, disable in production.
  • Error Handling: Always wrap connect() in try-catch and handle errors gracefully.
  • Health Checks: Implement health check endpoints that verify database connectivity.
  • Connection Pooling: arangojs handles connection pooling automatically; no manual management needed.
  • Graceful Shutdown: Although not required, you can implement cleanup logic for graceful shutdown.
  • Logging: Log connection events (success, failure, retries) for debugging and monitoring.
  • TypeScript: Use TypeScript for type safety and better IDE support.

Troubleshooting

Connection Refused

  • Verify ArangoDB server is running
  • Check URL and port are correct
  • Verify firewall settings

Authentication Failed

  • Verify username and password are correct
  • Check user has access to the database
  • Verify authentication method (basic auth vs. JWT)

Database Not Found

  • Enable autoCreateDatabase for development
  • Manually create database in ArangoDB web interface
  • Verify database name spelling

Slow Connections

  • Check network latency
  • Verify connection caching is working (should reuse connections)
  • Consider using connection pooling with custom HTTP agent
📚 API Reference: For complete API documentation including all exports and TypeScript types, see Connection Module API Reference.
Next: Learn about Models & Schemas to define your data structures.