Skip to contents

Get Productive in 10 Minutes

This quickstart gets you from zero to querying data in minutes. We’ll build a simple artist dashboard that demonstrates the core artutils workflow.

Prerequisites

Before starting, ensure you have:

  1. Database access: artutils connects to the Artalytics PostgreSQL database
  2. Environment variables configured: Database credentials must be set
  3. artcore installed: artutils depends on artcore for infrastructure

Step 1: Validate Your Environment

Always start by validating your setup. This catches configuration issues early:

library(artutils)
library(artcore)

# Check database connection
check_env_dba()
# Returns TRUE if all database env vars are set correctly

# Test the connection
cn <- artcore::dbc()
cat("Connected to database successfully\n")
artcore::dbd(cn)

What to check: - ART_PGHOST - Database host - ART_PGPORT - Database port (usually 5432) - ART_PGUSER - Your database username - ART_PGPASS - Your database password - ART_PGDATA - Database name

If check_env_dba() fails, see the artcore README for configuration instructions.

Step 2: Your First Query

Let’s find an artist and get their basic info:

# Look up artist by URL slug (the human-friendly identifier)
artist_record <- get_artist_by_slug("bobby-fatemi")

# Check if artist exists
if (is.null(artist_record)) {
  stop("Artist not found - check the slug spelling")
}

# Extract the UUID for subsequent queries
artist <- artist_record$artist_uuid

# Display basic info
cat("Found:", artist_record$artist_name, "\n")
cat("UUID:", artist, "\n")
cat("Location:", artist_record$city, ",", artist_record$state, "\n")
cat("Member since:", format(artist_record$created_utc, "%Y-%m-%d"), "\n")

Key pattern: artutils functions return NULL for missing data instead of throwing errors during lookup. Always check for NULL before proceeding.

Step 3: Gather Artist Statistics

Now get aggregate statistics for the profile header:

# Get summary statistics
stats <- get_artist_stats(artist)

cat("Portfolio size:", stats$artworks, "artworks\n")
cat("Collections:", stats$collections, "\n")
cat("Total hours:", round(stats$total_hours, 1), "\n")
cat("Total strokes:", format(stats$total_brushes, big.mark = ","), "\n")

These statistics are pre-aggregated in the database for performance. They’re updated by update_artist_stats() after modifications.

Step 4: Build a Simple Dashboard

Let’s combine everything into a reusable dashboard function:

build_artist_dashboard <- function(slug) {
  # Step 1: Resolve slug
  artist_record <- get_artist_by_slug(slug)
  if (is.null(artist_record)) {
    return(list(error = "artist_not_found", slug = slug))
  }

  artist <- artist_record$artist_uuid

  # Step 2: Gather data (reuse connection for efficiency)
  cn <- dbc()
  on.exit(dbd(cn))

  stats <- get_artist_stats(artist, cn = cn)
  collections <- get_artist_collections_summary(artist, cn = cn)
  recent_works <- get_artist_recent_works(artist, limit = 6, cn = cn)

  # Step 3: Build dashboard structure
  list(
    artist = list(
      name = artist_record$artist_name,
      location = paste(artist_record$city, artist_record$state, sep = ", "),
      bio = artist_record$bio,
      avatar_url = path_artist_thumb(artist),
      social = list(
        instagram = artist_record$url_ig,
        website = artist_record$url_site
      )
    ),
    portfolio = list(
      artworks = stats$artworks,
      collections = stats$collections,
      total_hours = stats$total_hours,
      total_brushes = stats$total_brushes,
      avg_hours_per_work = stats$total_hours / max(stats$artworks, 1)
    ),
    collections = collections,
    recent_works = recent_works
  )
}

# Use it
dashboard <- build_artist_dashboard("bobby-fatemi")
if (is.null(dashboard$error)) {
  cat("\n=== ARTIST DASHBOARD ===\n")
  cat("Name:", dashboard$artist$name, "\n")
  cat("Location:", dashboard$artist$location, "\n")
  cat("Portfolio:", dashboard$portfolio$artworks, "artworks\n")
  cat("Avg hours/work:", round(dashboard$portfolio$avg_hours_per_work, 1), "\n")
  cat("Recent works:", nrow(dashboard$recent_works), "\n")
}

Step 5: Add Asset URLs

Path functions construct CDN URLs for images and media:

# These are pure string operations - no database queries
avatar_url <- path_artist_thumb(artist)
cat("Avatar URL:", avatar_url, "\n")

# Get thumbnails for recent works
recent_works <- get_artist_recent_works(artist, limit = 3)
for (i in seq_len(nrow(recent_works))) {
  artwork_uuid <- recent_works$art_uuid[i]
  title <- recent_works$art_title[i]
  thumb_url <- path_artwork_thumb(artist, artwork_uuid)

  cat("\n", title, "\n")
  cat("  Thumbnail:", thumb_url, "\n")
  cat("  Main image:", path_art_main_image(artist, artwork_uuid), "\n")
}

Common Pitfalls & Solutions

Pitfall 1: Forgetting to Close Connections

Problem:

cn <- dbc()
result <- get_artist_stats(artist, cn = cn)
# Forgot to close connection!

Solution:

cn <- dbc()
on.exit(dbd(cn))  # Ensures cleanup even if error occurs
result <- get_artist_stats(artist, cn = cn)
# Connection closes when function exits

Pitfall 2: Not Checking for NULL

Problem:

artist_record <- get_artist_by_slug("nonexistent-artist")
artist <- artist_record$artist_uuid  # ERROR: $ operator on NULL

Solution:

artist_record <- get_artist_by_slug("nonexistent-artist")
if (is.null(artist_record)) {
  cat("Artist not found\n")
  # Handle 404 case
} else {
  artist <- artist_record$artist_uuid
  # Proceed with valid data
}

Pitfall 3: Using Deprecated Function Names

After v0.18.0, all functions use snake_case. Old names throw errors:

Problem:

# This will ERROR in v0.18.0+
stats <- getArtistStats(artist)

Solution:

# Use snake_case names
stats <- get_artist_stats(artist)
collections <- get_artist_collections_summary(artist)
recent <- get_artist_recent_works(artist, limit = 6)

Pitfall 4: Inefficient Connection Management

Problem:

# Opens and closes connection 3 times (150ms overhead)
stats <- get_artist_stats(artist)  # Open, query, close
collections <- get_artist_collections_summary(artist)  # Open, query, close
recent <- get_artist_recent_works(artist, limit = 6)  # Open, query, close

Solution:

# Reuse single connection (50ms overhead)
cn <- dbc()
on.exit(dbd(cn))
stats <- get_artist_stats(artist, cn = cn)
collections <- get_artist_collections_summary(artist, cn = cn)
recent <- get_artist_recent_works(artist, limit = 6, cn = cn)

Quick Reference: Essential Functions

Artist Lookup

get_artist_by_slug("artist-slug")      # Find by URL slug
get_artist_index(artist_uuid)          # Get full profile
get_artist_stats(artist_uuid)          # Portfolio statistics

Collections & Artworks

get_artist_collections_summary(artist_uuid)         # Collections with counts
get_artist_recent_works(artist_uuid, limit = 10)    # Recent artworks
get_artwork_index(artist_uuid, artwork_uuid)        # Artwork basics
get_artwork_stats(artist_uuid, artwork_uuid)        # Performance metrics

CDN Paths

path_artist_thumb(artist_uuid)                      # Artist avatar
path_artwork_thumb(artist_uuid, artwork_uuid)       # Artwork thumbnail
path_art_main_image(artist_uuid, artwork_uuid)      # Full-size image
path_replay_frame(artist_uuid, artwork_uuid, 1)     # Replay frame

Aggregated Data (Shiny Apps)

get_appdata(artist_uuid, artwork_uuid)  # Everything for artwork detail view

Next Steps

Now that you can query basic data:

Troubleshooting

“Connection refused” error

Check your database environment variables:

Sys.getenv("ART_PGHOST")
Sys.getenv("ART_PGPORT")
Sys.getenv("ART_PGUSER")
# Never print ART_PGPASS - it's a secret!

“Artist not found” for valid slug

The slug may have changed. Search by name instead:

# Custom query for name search
results <- db_art_get("
  SELECT artist_uuid, artist_name, slug
  FROM app.artist_index
  WHERE artist_name ILIKE '%search term%'
  LIMIT 5
")

Slow queries

Use shared connections for multiple queries:

# Don't open/close for each query
cn <- dbc()
on.exit(dbd(cn))
# ... all queries use cn = cn parameter

For production Shiny apps, consider using the pool package for connection pooling.