Getting Started with Streams API
This guide will walk you through setting up and using the Streams API to fetch and display content from your Streams Platform organization.
Prerequisites
- Node.js 16+ and npm/yarn
- TypeScript knowledge (recommended)
- A Streams Platform organization with published content
- Access to Web Studio for client generation
Step 1: Generate Your API Client
The Streams API client is automatically generated through Web Studio based on your organization's content:
- Navigate to Web Studio in your Streams Platform dashboard
- Your API client will be automatically generated with:
- Organization ID and Site ID pre-configured
- GraphQL queries for your content types
- TypeScript type definitions
- Complete client implementation
Step 2: Client Structure
Your generated client will include several key files:
streams-api-client/
├── streams-content-api-client.ts # Main client class
├── types.ts # TypeScript definitions
├── postsquery.graphql # Posts query
├── articlequery.graphql # Articles query
├── contentlistsquery.graphql # Content lists query
└── adcampaignsquery.graphql # Ad campaigns query
Step 3: Use in Your Application
SvelteKit Example
// src/routes/+page.ts
import { mapApiArticleToArticlePreview } from "$lib/posts/mapper";
import { StreamsContentApiClient } from "../../../streams-api-client/streams-content-api-client";
import type { Post } from "../../../streams-api-client/types";
import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ params }) => {
const apiContentClient = new StreamsContentApiClient();
// Get all content lists
const contentLists = (await apiContentClient.getContentLists()) ?? [];
const contentListsMap = new Map(contentLists.map(({ name, id }) => [name, id]));
// Helper function to fetch and map content
const fetchAndMapContent = async <T>(
listName: string,
mapper: (post: Post) => T
): Promise<T[]> => {
if (!contentListsMap.has(listName)) {
return [];
}
const listId = contentListsMap.get(listName);
const apiContent = (await apiContentClient.getPosts(null, [], listId)) ?? [];
return apiContent.map(mapper);
};
// Fetch content from specific lists
const [news, featuredPosts, videos] = await Promise.all([
fetchAndMapContent('Latest News', mapApiArticleToArticlePreview),
fetchAndMapContent('Featured Content', mapApiArticleToArticlePreview),
apiContentClient.getPosts(null, ['SHORT_VIDEO'], null)
]);
return {
news,
featuredPosts,
videos: videos ?? []
};
};
<!-- src/routes/+page.svelte -->
<script lang="ts">
import Articles3_1 from '../components/article/articles3_1.svelte';
import Articles3_2 from '../components/article/articles3_2.svelte';
import VideoGrid from '../components/video/video_grid.svelte';
import type { PageData } from './$types';
export let data: PageData;
let { news, featuredPosts, videos } = data;
</script>
<div class="container">
<!-- Featured News Section -->
<section class="news-section">
<h2>Latest News</h2>
<Articles3_1 articles={news.slice(0, 3)} />
<Articles3_2 articles={news.slice(3, 6)} />
</section>
<!-- Featured Content -->
<section class="featured-section">
<h2>Featured Content</h2>
{#each featuredPosts as post}
<article class="featured-post">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
{#if post.media}
<img src={post.media.thumbnailUrl} alt={post.title} />
{/if}
</article>
{/each}
</section>
<!-- Video Content -->
{#if videos.length > 0}
<section class="videos-section">
<h2>Latest Videos</h2>
<VideoGrid {videos} />
</section>
{/if}
</div>
<style>
.container {
max-width: 1221px;
margin: 0 auto;
padding: 2rem;
}
.news-section, .featured-section, .videos-section {
margin-bottom: 3rem;
}
h2 {
font-size: 2rem;
margin-bottom: 1.5rem;
color: #2563eb;
}
.featured-post {
margin-bottom: 2rem;
padding: 1.5rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
}
.featured-post img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
margin-top: 1rem;
}
</style>
React/Next.js Example
// pages/index.tsx
import { GetStaticProps } from 'next';
import { StreamsContentApiClient } from '../lib/streams-api-client/streams-content-api-client';
import type { Post } from '../lib/streams-api-client/types';
interface HomeProps {
posts: Post[];
articles: any[];
}
export default function Home({ posts, articles }: HomeProps) {
return (
<div className="container">
<section>
<h1>Latest Posts</h1>
{posts.map((post) => (
<article key={post.id} className="post-card">
<h2>{post.postData.articleData?.title || 'Post'}</h2>
{post.postData.media && (
<img
src={post.postData.media.thumbnailUrl}
alt={post.postData.articleData?.title || 'Post image'}
/>
)}
<p>{post.postData.textData?.text}</p>
</article>
))}
</section>
</div>
);
}
export const getStaticProps: GetStaticProps = async () => {
const client = new StreamsContentApiClient();
try {
const posts = await client.getPosts(null, ['REGULAR_POST', 'SHORT_VIDEO']);
const contentLists = await client.getContentLists();
return {
props: {
posts: posts || [],
contentLists: contentLists || []
},
revalidate: 60 // Revalidate every minute
};
} catch (error) {
console.error('Failed to fetch content:', error);
return {
props: {
posts: [],
contentLists: []
}
};
}
};
Step 4: Advanced Features
Content Type Filtering
// Get only short videos
const shortVideos = await apiClient.getPosts(null, ['SHORT_VIDEO']);
// Get only articles
const articles = await apiClient.getPosts(null, ['ARTICLE']);
// Get mixed content types
const mixedContent = await apiClient.getPosts(null, ['REGULAR_POST', 'SHORT_VIDEO', 'ARTICLE']);
Search Functionality
// Search across all content
const searchResults = await apiClient.getPosts(null, null, null, 'sustainability');
// Search within a specific content list
const contentListId = 'your-content-list-id';
const searchInList = await apiClient.getPosts(null, null, contentListId, 'climate change');
Working with Rich Media
// Access media with tags and metadata
posts.forEach(post => {
if (post.postData.media) {
const media = post.postData.media;
// Display image with proper scaling
console.log('Image URL:', media.url);
console.log('Thumbnail:', media.thumbnailUrl);
console.log('Dimensions:', media.metadata.width, 'x', media.metadata.height);
// Handle product tags for e-commerce
media.productTags?.forEach(tag => {
console.log('Product:', tag.product.name, tag.product.price);
console.log('Position:', tag.leftPCT, tag.topPCT);
});
// Handle user tags for social features
media.userTags?.forEach(tag => {
console.log('Tagged user:', tag.user.name);
console.log('Position:', tag.leftPCT, tag.topPCT);
});
}
});
Article Content Structure
// Working with structured articles
const article = await apiClient.getArticle('my-article-slug');
if (article) {
console.log('Title:', article.id);
console.log('Published:', article.publishedAt);
console.log('Main image:', article.mainImage);
// Process article sections
article.sections.forEach(section => {
section.layouts.forEach(layout => {
layout.fields.forEach(field => {
switch (field.fieldData.type) {
case 'RICH_TEXT':
console.log('Rich text:', field.fieldData.richTextData?.text);
break;
case 'IMAGE':
console.log('Image:', field.fieldData.imageData?.image.url);
break;
case 'VIDEO':
console.log('Video:', field.fieldData.videoData?.video.url);
break;
case 'MEDIA_GALLERY':
console.log('Gallery items:', field.fieldData.mediaGalleryData?.media.length);
break;
}
});
});
});
}
Error Handling
try {
const posts = await apiClient.getPosts(null, ['REGULAR_POST']);
if (!posts || posts.length === 0) {
console.log('No posts found');
return;
}
// Process posts
posts.forEach(post => {
console.log('Post:', post.id);
});
} catch (error) {
console.error('Failed to fetch posts:', error);
// Handle specific GraphQL errors
if (error instanceof Error) {
try {
const graphqlErrors = JSON.parse(error.message);
console.error('GraphQL errors:', graphqlErrors);
} catch (parseError) {
console.error('Network or other error:', error.message);
}
}
}
Best Practices
- Use Content Lists: Organize content using content lists for better performance and organization
- Implement Caching: Use the
X-Use-Cache
header and implement client-side caching - Handle Loading States: Always show loading indicators while fetching content
- Optimize Images: Use appropriate image scales based on your layout requirements
- Error Boundaries: Implement proper error handling in your UI components
- Type Safety: Leverage the generated TypeScript types for better development experience
Performance Tips
- Batch Requests: Fetch related content in parallel using
Promise.all()
- Image Optimization: Use thumbnail URLs for previews and full URLs for detail views
- Pagination: Implement pagination for large content sets
- Selective Fetching: Only fetch the content types you need