Skip to content

GraphQL Examples & SDKs

This guide provides client setup examples in cURL, JavaScript, and Python, along with 23 targeted query examples (recipes) to jumpstart your Nostalgia GraphQL API integration.


Client Setup

curl -X POST https://api.nostalgialab.org/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer nlk_YOUR_API_KEY" \
  -d '{"query": "{ releases(system: \"NES\", limit: 5) { title score } }"}'
const API_KEY = 'nlk_YOUR_API_KEY';
const ENDPOINT = 'https://api.nostalgialab.org/';

async function gql(query, variables = {}) {
  const res = await fetch(ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`
    },
    body: JSON.stringify({ query, variables })
  });
  if (res.status === 403) throw new Error('Invalid or missing API key');
  if (!res.ok) throw new Error(`HTTP error! Status: ${res.status}`);
  const json = await res.json();
  if (json.errors) throw new Error(json.errors[0].message);
  return json.data;
}
import requests

API_KEY = 'nlk_YOUR_API_KEY'
ENDPOINT = 'https://api.nostalgialab.org/'

def gql(query, variables=None):
    resp = requests.post(
        ENDPOINT,
        json={'query': query, 'variables': variables or {}},
        headers={'Authorization': f'Bearer {API_KEY}'}
    )
    if resp.status_code == 403:
        raise Exception('Invalid or missing API key')
    resp.raise_for_status()
    data = resp.json()
    if 'errors' in data:
        raise Exception(data['errors'][0]['message'])
    return data['data']

Query Recipes

1. Get a Single Release by ID

{
  release(id: 12345) {
    releaseId
    title
    system
    systemName
    region
    genre
    genre2
    developer
    publisher
    releaseDate
    dateUs
    dateJp
    dateEu
    score
    players
    online
    discCount
    series
    seriesName
    description
    advancedDescription
  }
}

2. List Releases by System

{
  releases(system: "SNES", limit: 20) {
    releaseId
    title
    score
    developer
    publisher
    releaseDate
    genre
  }
}

Available system codes: NES SNES N64 NDS GBA GB GBC GEN SMS GG SAT PS1 PSP TGCD Amiga Atari2600 Atari5200 Atari7800 Lynx C64 Coleco NGP PC MD SEGACD VB WS


3. Filter by Genre

Genre is matched case-insensitively using ILIKE, so "rpg" and "Action RPG" both work.

{
  releases(system: "SNES", genre: "RPG", limit: 25) {
    releaseId
    title
    score
    developer
    genre
    genre2
  }
}

4. Filter by Region

{
  releases(system: "NES", region: "Japan", limit: 20) {
    releaseId
    title
    region
    dateJp
    developer
  }
}

Common region values: USA Japan Europe World Korea


5. Search by Title

Searches across all platforms. Uses ILIKE so partial matches work.

{
  releases(search: "Your Search Term", limit: 20) {
    releaseId
    title
    system
    region
    score
    developer
  }
}

6. Combine Multiple Filters

{
  releases(
    system: "GBA"
    genre: "Action"
    region: "USA"
    limit: 15
    offset: 0
  ) {
    releaseId
    title
    score
    genre
    developer
    publisher
  }
}

7. Pagination

Max limit is 100. Use offset to step through large result sets.

{
  releases(system: "NES", limit: 100, offset: 100) {
    releaseId
    title
    score
  }
}

JavaScript pagination loop:

async function getAllReleases(system) {
  const results = [];
  let offset = 0;
  const limit = 100;
  while (true) {
    const data = await gql(`{
      releases(system: "${system}", limit: ${limit}, offset: ${offset}) {
        releaseId title score developer
      }
    }`);
    results.push(...data.releases);
    if (data.releases.length < limit) break;
    offset += limit;
  }
  return results;
}

const allNes = await getAllReleases('NES');
console.log(`${allNes.length} NES games indexed`);

8. Get Box Art and Images

Each image type returns a presigned S3 URL valid for 15 minutes. null means not available for that release.

{
  release(id: 12345) {
    title
    imageUrl(type: front)
  }
}

Get every image type at once using field aliases:

{
  release(id: 12345) {
    title
    front:           imageUrl(type: front)
    back:            imageUrl(type: back)
    cart:            imageUrl(type: cart)
    main:            imageUrl(type: main)
    screenshot:      imageUrl(type: screenshot)
    screenshottitle: imageUrl(type: screenshottitle)
    manual:          imageUrl(type: manual)
    wheel:           imageUrl(type: wheel)
    wheelcarbon:     imageUrl(type: wheelcarbon)
    wheelsteel:      imageUrl(type: wheelsteel)
    box3d:           imageUrl(type: box3d)
    boxtexture:      imageUrl(type: boxtexture)
    marquee:         imageUrl(type: marquee)
    marqueesmall:    imageUrl(type: marqueesmall)
    side:            imageUrl(type: side)
    video:           imageUrl(type: video)
  }
}

All ImageType values:

Value Description
front Front box art
back Back of box
cart Cartridge or disc label
main Primary image (best available)
screenshot In-game screenshot
screenshottitle Title screen screenshot
texture Box texture for 3D rendering
video Video thumbnail
manual Game manual scan
wheel Logo / wheel art (transparent PNG)
wheelcarbon Wheel art on carbon background
wheelsteel Wheel art on steel background
marquee Arcade marquee banner
marqueesmall Small marquee
smallmarquee Alternative small marquee format
box3d 3D rendered box image
boxtexture Raw box texture map
side Side of box

Presigned Image URL Lifespan

Presigned image and video URLs are valid for 15 minutes (900 seconds) from time of generation. Do not store or cache these URLs permanently; query the API dynamically to obtain fresh URLs.


9. Get Video URL

{
  release(id: 12345) {
    title
    videoUrl
  }
}

Returns a presigned URL valid for 15 minutes. null if no video exists for this release.


10. Get All Reviews for a Game

{
  release(id: 12345) {
    title
    score
    reviews {
      id
      title
      body
      rating
      source
      author
      date
      platform
      url
      type
      createdAt
    }
  }
}

11. Get Regional Variants

Find all regional editions of the same game (USA, Japan, Europe releases):

{
  release(id: 12345) {
    title
    region
    otherRegions {
      releaseId
      title
      region
      dateUs
      dateJp
      dateEu
    }
  }
}

12. Get Other Platform Releases

Same game released on multiple consoles:

{
  release(id: 12345) {
    title
    system
    otherSystems {
      releaseId
      title
      system
      systemName
      region
      score
    }
  }
}

13. Get ROM Data for a Release

{
  release(id: 12345) {
    title
    rom {
      romId
      filename
      filenameNoExt
      size
      crc
      md5
      sha1
      region
      language
      serial
      dumpSource
    }
  }
}

14. Identify a ROM by MD5 Hash

Useful for matching a file you already have to its database entry:

{
  roms(md5: "abc123def456abc123def456abc12345", limit: 5) {
    romId
    filename
    size
    crc
    md5
    sha1
    releases {
      releaseId
      title
      system
      region
      score
    }
  }
}

Also works with crc or sha1:

{
  roms(crc: "A1B2C3D4") {
    romId
    filename
    releases { title system }
  }
}

15. Look Up ROMs by Filename

{
  roms(filename: "your-rom-filename", limit: 10) {
    romId
    filename
    size
    sha1
    releases {
      releaseId
      title
      system
      region
    }
  }
}

16. Fetch a ROM by ID

{
  rom(id: 9876) {
    romId
    filename
    filenameNoExt
    size
    crc
    md5
    sha1
    language
    region
    serial
    parent
    header
    dumpSource
    system {
      name
      shortName
      manufacturer
    }
    releases {
      releaseId
      title
      region
      score
    }
  }
}

17. Get System Metadata and SQLite Download URL

The downloadUrl field returns a presigned S3 URL for the NostalgiaDB SQLite file for that system, valid for 1 hour:

{
  releases(system: "SNES", limit: 1) {
    system {
      systemId
      name
      shortName
      manufacturer
      releaseYear
      generation
      mediaType
      region
      unitsSold
      specs
      description
      downloadUrl
    }
  }
}

18. Complete Game Detail Query

Everything needed to render a full game page -- metadata, images, reviews, and cross-references:

{
  release(id: 12345) {
    releaseId
    title
    system
    systemName
    region
    genre
    genre2
    developer
    publisher
    releaseDate
    dateUs
    dateJp
    dateEu
    score
    players
    online
    discCount
    description
    advancedDescription
    front:      imageUrl(type: front)
    back:       imageUrl(type: back)
    screenshot: imageUrl(type: screenshot)
    manual:     imageUrl(type: manual)
    videoUrl
    reviews {
      rating
      source
      author
      date
      url
    }
    otherRegions {
      releaseId
      region
    }
    otherSystems {
      releaseId
      system
      systemName
    }
    rom {
      filename
      size
      md5
      sha1
    }
  }
}

19. Find Games in a Series / Franchise

{
  releases(search: "Game Series Name", system: "NES", limit: 20) {
    releaseId
    title
    series
    seriesName
    releaseDate
    score
    developer
  }
}

All games sharing the same series value belong to the same franchise.


20. Python: Identify a ROM File by Hash

import hashlib

def file_md5(path):
    h = hashlib.md5()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(8192), b''):
            h.update(chunk)
    return h.hexdigest()

def identify_rom(path):
    md5 = file_md5(path)
    result = gql(f'''{{
      roms(md5: "{md5}", limit: 5) {{
        romId filename size
        releases {{
          releaseId title system region score
        }}
      }}
    }}''')
    for rom in result['roms']:
        print(f"File: {rom['filename']} ({rom['size']:,} bytes)")
        for release in rom['releases']:
            print(f"  {release['title']} ({release['system']}, {release['region']}) Score: {release['score']}")

identify_rom('/path/to/your/rom.sfc')

21. Python: Download All Box Art for a System

import os, requests as req

def download_box_art(system, output_dir):
    os.makedirs(output_dir, exist_ok=True)
    offset, limit, count = 0, 50, 0
    while True:
        result = gql(f'''{{
          releases(system: "{system}", limit: {limit}, offset: {offset}) {{
            releaseId
            title
            imageUrl(type: front)
          }}
        }}''')
        for release in result['releases']:
            url = release.get('imageUrl')
            if not url:
                continue
            path = os.path.join(output_dir, f"{release['releaseId']}.jpg")
            if not os.path.exists(path):
                img = req.get(url, timeout=30)
                if img.status_code == 200:
                    open(path, 'wb').write(img.content)
                    count += 1
        if len(result['releases']) < limit:
            break
        offset += limit
    print(f'Downloaded {count} images for {system}')

download_box_art('SNES', './art/snes')

Presigned URL Expiration

Image URLs expire in 15 minutes. Download them immediately -- do not store the URL and download later.


22. JavaScript: Paginated Game Browser

async function getBrowsePage(system, page = 0) {
  const limit = 24;
  const data = await gql(`{
    releases(system: "${system}", limit: ${limit}, offset: ${page * limit}) {
      releaseId title score developer genre players
      imageUrl(type: front)
    }
  }`);
  return { games: data.releases, hasMore: data.releases.length === limit };
}

const { games } = await getBrowsePage('SNES', 0);
games.forEach(g => console.log(`${g.title} [${g.score}] - ${g.genre}`));

23. JavaScript: Build a Local Search Index

Fetch all releases for a system and build a fast client-side title lookup:

async function buildIndex(system) {
  const all = [];
  let offset = 0;
  while (true) {
    const data = await gql(`{
      releases(system: "${system}", limit: 100, offset: ${offset}) {
        releaseId title genre developer publisher score region
      }
    }`);
    all.push(...data.releases);
    if (data.releases.length < 100) break;
    offset += 100;
  }
  const byTitle = {};
  for (const r of all) {
    const k = r.title.toLowerCase();
    (byTitle[k] = byTitle[k] || []).push(r);
  }
  return { all, byTitle };
}

const index = await buildIndex('NES');
const hits = index.byTitle['your game title'] ?? [];
console.log(`Found ${index.all.length} NES games. Matched title: ${hits.length} version(s)`);