← Back to API Documentation Main Documentation

API Integration Examples

Created: Tue 29 Jul 2025 07:29:47 CEST
Document Version: 1.0 - Initial code examples and integration patterns
Security Classification: Public Technical Documentation
Target Audience: Frontend Developers, Backend Developers, Integration Engineers
Author: Paul Wisén

Overview

This document provides practical code examples and integration patterns for working with the Plings GraphQL API. All examples use the production endpoint at https://api.plings.io/graphql.

Authentication Examples

JWT Token Authentication

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

// Create authenticated Apollo client
const httpLink = createHttpLink({
  uri: 'https://api.plings.io/graphql',
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('plings_auth_token');
  
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

Login Flow

import { gql } from '@apollo/client';

const LOGIN_MUTATION = gql`
  mutation Login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
      token
      user {
        id
        email
        username
        organizations {
          id
          name
          role
        }
      }
    }
  }
`;

async function loginUser(email: string, password: string) {
  try {
    const { data } = await client.mutate({
      mutation: LOGIN_MUTATION,
      variables: { email, password }
    });
    
    // Store token for future requests
    localStorage.setItem('plings_auth_token', data.login.token);
    
    return data.login.user;
  } catch (error) {
    console.error('Login failed:', error);
    throw error;
  }
}

Object Management Examples

Creating Objects

const CREATE_OBJECT = gql`
  mutation CreateObject($input: CreateObjectInput!) {
    createObject(input: $input) {
      success
      object {
        id
        name
        plingsPath
        instanceKey
        objectClass {
          name
          description
        }
        owner {
          username
        }
      }
      identifier {
        value
        qrCodeUrl
      }
      error {
        message
        code
      }
    }
  }
`;

async function createNewObject(objectData: CreateObjectInput) {
  const { data } = await client.mutate({
    mutation: CREATE_OBJECT,
    variables: { input: objectData }
  });
  
  if (data.createObject.success) {
    return {
      object: data.createObject.object,
      qrCode: data.createObject.identifier.qrCodeUrl
    };
  } else {
    throw new Error(data.createObject.error.message);
  }
}

// Example usage
const newObject = await createNewObject({
  name: "My Mountain Bike",
  description: "Trek X-Caliber 8",
  classId: "class-mountain-bike-uuid",
  images: ["https://example.com/bike.jpg"]
});

Querying Objects

const GET_OBJECT_BY_ID = gql`
  query GetObject($id: ID!) {
    object(id: $id) {
      id
      name
      description
      status
      objectClass {
        name
        description
      }
      spatialLocation {
        id
        name
        path
      }
      currentLocation {
        id
        name
        type
      }
      owner {
        username
        organization {
          name
        }
      }
      images {
        url
        altText
      }
      relationships {
        type
        relatedObject {
          id
          name
        }
      }
    }
  }
`;

async function getObjectDetails(objectId: string) {
  const { data } = await client.query({
    query: GET_OBJECT_BY_ID,
    variables: { id: objectId }
  });
  
  return data.object;
}

Searching Objects

const SEARCH_OBJECTS = gql`
  query SearchObjects($query: String!, $filters: ObjectFilters) {
    searchObjects(query: $query, filters: $filters) {
      total
      objects {
        id
        name
        description
        objectClass {
          name
        }
        owner {
          username
        }
        images {
          url
          altText
        }
      }
    }
  }
`;

async function searchUserObjects(searchTerm: string, filters?: ObjectFilters) {
  const { data } = await client.query({
    query: SEARCH_OBJECTS,
    variables: { 
      query: searchTerm,
      filters: {
        ...filters,
        ownedByCurrentUser: true
      }
    }
  });
  
  return data.searchObjects;
}

QR Code and Identifier Examples

Resolving Scanned Identifiers

const RESOLVE_IDENTIFIER = gql`
  query ResolveIdentifier($instanceKey: String!, $path: String!) {
    resolveIdentifier(instanceKey: $instanceKey, path: $path) {
      type
      objectId
      classId
      canEdit
      object {
        id
        name
        description
        status
        owner {
          username
        }
        images {
          url
        }
      }
    }
  }
`;

async function handleQRScan(scannedUrl: string) {
  // Parse QR code URL
  const url = new URL(scannedUrl);
  const instanceKey = url.searchParams.get('i');
  const path = url.searchParams.get('p');
  
  if (!instanceKey || !path) {
    throw new Error('Invalid QR code format');
  }
  
  const { data } = await client.query({
    query: RESOLVE_IDENTIFIER,
    variables: { instanceKey, path }
  });
  
  const result = data.resolveIdentifier;
  
  switch (result.type) {
    case 'KNOWN_OBJECT':
      return { type: 'object', data: result.object };
    case 'UNKNOWN_GENERAL':
      return { type: 'create', data: { instanceKey, path } };
    case 'UNKNOWN_SPECIFIC':
      return { type: 'claim', data: { instanceKey, path, classId: result.classId } };
    default:
      throw new Error('Unknown identifier type');
  }
}

Creating Scan Events

const CREATE_SCAN_EVENT = gql`
  mutation CreateScanEvent($input: ScanEventInput!) {
    createScanEvent(input: $input) {
      id
      timestamp
      location {
        latitude
        longitude
      }
    }
  }
`;

async function logScanEvent(instanceKey: string, location?: GeolocationPosition) {
  const scanData: ScanEventInput = {
    instanceKey,
    timestamp: new Date().toISOString(),
    context: 'mobile_app'
  };
  
  if (location) {
    scanData.location = {
      latitude: location.coords.latitude,
      longitude: location.coords.longitude,
      accuracy: location.coords.accuracy
    };
  }
  
  await client.mutate({
    mutation: CREATE_SCAN_EVENT,
    variables: { input: scanData }
  });
}

Spatial Relationship Examples

Managing Object Relationships

const UPDATE_SPATIAL_LOCATION = gql`
  mutation UpdateSpatialLocation($objectId: ID!, $containerId: ID!) {
    updateSpatialLocation(objectId: $objectId, containerId: $containerId) {
      success
      object {
        id
        spatialLocation {
          id
          name
          path
        }
      }
    }
  }
`;

async function moveObjectToContainer(objectId: string, containerId: string) {
  const { data } = await client.mutate({
    mutation: UPDATE_SPATIAL_LOCATION,
    variables: { objectId, containerId }
  });
  
  return data.updateSpatialLocation;
}

Querying Spatial Hierarchies

const GET_CONTAINER_CONTENTS = gql`
  query GetContainerContents($containerId: ID!) {
    object(id: $containerId) {
      id
      name
      containedObjects {
        id
        name
        objectClass {
          name
        }
        images {
          url
        }
      }
      subContainers {
        id
        name
        type
        containedObjects {
          id
          name
        }
      }
    }
  }
`;

async function getContainerContents(containerId: string) {
  const { data } = await client.query({
    query: GET_CONTAINER_CONTENTS,
    variables: { containerId }
  });
  
  return {
    container: data.object,
    objects: data.object.containedObjects,
    subContainers: data.object.subContainers
  };
}

Error Handling Patterns

GraphQL Error Handling

import { ApolloError } from '@apollo/client';

function handleGraphQLError(error: ApolloError) {
  if (error.networkError) {
    // Network connectivity issues
    console.error('Network error:', error.networkError);
    return { type: 'network', message: 'Connection failed. Please check your internet.' };
  }
  
  if (error.graphQLErrors.length > 0) {
    const graphqlError = error.graphQLErrors[0];
    
    switch (graphqlError.extensions?.code) {
      case 'UNAUTHENTICATED':
        // Redirect to login
        localStorage.removeItem('plings_auth_token');
        return { type: 'auth', message: 'Please log in again.' };
        
      case 'FORBIDDEN':
        return { type: 'permission', message: 'You don\'t have permission for this action.' };
        
      case 'NOT_FOUND':
        return { type: 'not_found', message: 'The requested item was not found.' };
        
      default:
        return { type: 'unknown', message: graphqlError.message };
    }
  }
  
  return { type: 'unknown', message: 'An unexpected error occurred.' };
}

Retry Logic for Failed Requests

async function withRetry<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  delay: number = 1000
): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
      
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
    }
  }
  
  throw new Error('Max retries exceeded');
}

// Usage
const objectData = await withRetry(() => getObjectDetails(objectId));

Real-time Updates

GraphQL Subscriptions

const OBJECT_UPDATES = gql`
  subscription ObjectUpdates($objectId: ID!) {
    objectUpdated(objectId: $objectId) {
      id
      name
      status
      spatialLocation {
        id
        name
      }
      lastModified
    }
  }
`;

function subscribeToObjectUpdates(objectId: string, onUpdate: (object: any) => void) {
  const subscription = client.subscribe({
    query: OBJECT_UPDATES,
    variables: { objectId }
  }).subscribe({
    next: ({ data }) => {
      onUpdate(data.objectUpdated);
    },
    error: (error) => {
      console.error('Subscription error:', error);
    }
  });
  
  return () => subscription.unsubscribe();
}

TypeScript Type Definitions

Generated Types

// Auto-generated from GraphQL schema
export interface CreateObjectInput {
  name: string;
  description?: string;
  classId: string;
  images?: string[];
  spatialLocationId?: string;
}

export interface ObjectFilters {
  classId?: string;
  ownedByCurrentUser?: boolean;
  status?: ObjectStatus;
  spatialLocationId?: string;
}

export enum ObjectStatus {
  NORMAL = 'NORMAL',
  FOR_SALE = 'FOR_SALE',
  FOR_RENT = 'FOR_RENT',
  LOST = 'LOST',
  LENDABLE = 'LENDABLE'
}

export interface ScanEventInput {
  instanceKey: string;
  timestamp: string;
  context: string;
  location?: {
    latitude: number;
    longitude: number;
    accuracy?: number;
  };
}

Performance Optimization

Query Optimization with Fragments

const OBJECT_FRAGMENT = gql`
  fragment ObjectDetails on Object {
    id
    name
    description
    status
    objectClass {
      id
      name
    }
    owner {
      username
    }
    images {
      url
      altText
    }
  }
`;

const GET_OBJECTS_LIST = gql`
  query GetObjectsList($filters: ObjectFilters) {
    objects(filters: $filters) {
      ...ObjectDetails
    }
  }
  ${OBJECT_FRAGMENT}
`;

Caching Strategies

const cache = new InMemoryCache({
  typePolicies: {
    Object: {
      fields: {
        containedObjects: {
          merge(existing = [], incoming) {
            return incoming;
          }
        }
      }
    }
  }
});