GIF search in any JavaScript app
A framework-independent TypeScript client for Vue, Svelte, vanilla JavaScript and Node.js. Bring your own interface.
1. Install the client
npm install @rowix/gifsnap-jsInstall the JavaScript client from npm. TypeScript declarations are included.
The package has no runtime dependencies or React requirement. Use it with an ESM-capable bundler, modern browser, or Node.js 18 and later.
2. Search for a GIF
import { createGifSnapClient } from '@rowix/gifsnap-js';
const gifsnap = createGifSnapClient();
const { data, pagination } = await gifsnap.search({
query: 'hello', limit: 12,
});
console.log(data[0]?.url);Render gif.url to show animation. The preview_url can be a still image. Keep complete URLs, including query parameters, and use dimensions to reserve layout space.
3. Browse stickers or trending results
const trending = await gifsnap.trending({ limit: 12 });
const stickers = await gifsnap.searchStickers({
query: 'hello', limit: 12,
});
const trendingStickers = await gifsnap.trendingStickers();Cancel stale searches
Create an abort controller for the request and cancel it when the component unmounts or the search changes. Handle a failed request separately from an empty result.
import { createGifSnapClient, GifSnapError } from '@rowix/gifsnap-js';
const client = createGifSnapClient({ timeoutMs: 15000 });
const controller = new AbortController();
try {
const result = await client.search({
query: 'celebrate', signal: controller.signal,
});
console.log(result.data);
} catch (error) {
if (!controller.signal.aborted) {
console.error(error instanceof GifSnapError ? error.code : error);
}
}
// In your component cleanup: controller.abort();Load another page
const query = 'hello';
const limit = 12;
const first = await client.search({ query, limit });
if (first.pagination.has_next && first.pagination.next_page !== null) {
const next = await client.search({
query, limit, page: first.pagination.next_page,
});
console.log(next.data);
}Keep the query and limit unchanged. The client preserves raw API results and counts. In your UI, use an optional verified content_id, an exact item ID, or an exact URL to suppress repeated media while preserving first-seen order. Do not infer identity from titles or stripped filenames.
Vue and Svelte examples
These complete components use the same JavaScript client. They cancel superseded requests and return the selected media. Style the markup to fit your application.
Vue 3: GifSearch.vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { createGifSnapClient, type GifSnapGif } from '@rowix/gifsnap-js';
const emit = defineEmits<{ select: [gif: GifSnapGif] }>();
const client = createGifSnapClient();
const query = ref('hello'), items = ref<GifSnapGif[]>([]);
const loading = ref(false), error = ref('');
let controller: AbortController | undefined;
async function search() {
controller?.abort();
const request = new AbortController(); controller = request;
loading.value = true; error.value = ''; items.value = [];
try {
const options = { limit: 12, signal: request.signal };
const result = query.value.trim()
? await client.search({ ...options, query: query.value })
: await client.trending(options);
if (!request.signal.aborted) items.value = result.data;
} catch {
if (!request.signal.aborted) error.value = 'Could not load GIFs. Try again.';
} finally {
if (!request.signal.aborted) loading.value = false;
}
}
onMounted(() => { void search(); });
onUnmounted(() => controller?.abort());
</script>
<template>
<form @submit.prevent="search">
<label>Search GIFs <input v-model="query" type="search" maxlength="120" /></label>
<button type="submit">Search</button>
</form>
<p role="status">{{ loading ? 'Loading GIFs…' : `${items.length} GIFs` }}</p>
<p v-if="error" role="alert">{{ error }}</p>
<ul>
<li v-for="(gif, index) in items" :key="`${gif.id}:${index}`">
<button type="button" :aria-label="`Select ${gif.title || 'GIF'}`" @click="emit('select', gif)">
<img :src="gif.url" alt="" :width="gif.width || 240" :height="gif.height || 180" loading="lazy" />
{{ gif.title }}
</button>
</li>
</ul>
<a href="https://gifsnap.com" target="_blank" rel="noopener noreferrer">Powered by GifSnap</a>
</template>
Listen for selection with <GifSearch @select="handleSelect" />.
Svelte 5: GifSearch.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { createGifSnapClient, type GifSnapGif } from '@rowix/gifsnap-js';
let { onSelect }: { onSelect: (gif: GifSnapGif) => void } = $props();
const client = createGifSnapClient();
let query = $state('hello'), items = $state<GifSnapGif[]>([]);
let loading = $state(false), error = $state('');
let controller: AbortController | undefined;
async function search(event?: SubmitEvent) {
event?.preventDefault();
controller?.abort();
const request = new AbortController(); controller = request;
loading = true; error = ''; items = [];
try {
const options = { limit: 12, signal: request.signal };
const result = query.trim()
? await client.search({ ...options, query })
: await client.trending(options);
if (!request.signal.aborted) items = result.data;
} catch {
if (!request.signal.aborted) error = 'Could not load GIFs. Try again.';
} finally {
if (!request.signal.aborted) loading = false;
}
}
onMount(() => {
void search();
return () => controller?.abort();
});
</script>
<form onsubmit={search}>
<label>Search GIFs <input bind:value={query} type="search" maxlength="120" /></label>
<button type="submit">Search</button>
</form>
<p role="status">{loading ? 'Loading GIFs…' : `${items.length} GIFs`}</p>
{#if error}<p role="alert">{error}</p>{/if}
<ul>
{#each items as gif, index (`${gif.id}:${index}`)}
<li>
<button type="button" aria-label={`Select ${gif.title || 'GIF'}`} onclick={() => onSelect(gif)}>
<img src={gif.url} alt="" width={gif.width || 240} height={gif.height || 180} loading="lazy" />
{gif.title}
</button>
</li>
{/each}
</ul>
<a href="https://gifsnap.com" target="_blank" rel="noopener noreferrer">Powered by GifSnap</a>
Pass your callback with <GifSearch onSelect={handleSelect} />.
Use another language
The same REST API works from Flutter, Python, Go and other HTTP clients. For ready-made interfaces, use the React, Angular, SwiftUI or Compose picker.
SDK source and complete client reference ↗ · Response fields and errors