Skip to main content
StormyCMS

Examples

Practical examples and tutorials for using the StormyCMS API in real-world applications.

Table of Contents

Basic Setup

Install Required Packages

Terminal window
npm install graphql-request
# or
yarn add graphql-request

Create API Client

src/stormycms.js
import { GraphQLClient } from 'graphql-request';
class StormyCMS {
constructor(apiKey, clientId) {
this.client = new GraphQLClient('https://api.stormycms.com/graphql', {
headers: {
'x-site-api-key': apiKey,
'x-client-id': clientId,
},
});
}
async request(query, variables) {
try {
return await this.client.request(query, variables);
} catch (error) {
console.error('StormyCMS Error:', error);
throw error;
}
}
// Query methods
async getPages(limit = 20, offset = 0) {
const query = `
query GetPages($limit: Int, $offset: Int) {
pages(limit: $limit, offset: $offset) {
id
meta {
title
description
keywords
}
layout_id
components {
id
name
text
}
}
}
`;
return this.request(query, { limit, offset });
}
async getPage(id) {
const query = `
query GetPage($id: ID!) {
page(id: $id) {
id
meta {
title
description
keywords
}
components {
id
name
text
props
child_ids
}
}
}
`;
return this.request(query, { id });
}
// Mutation methods
async createPage(title, description, keywords = [], layoutId, components = []) {
const mutation = `
mutation CreatePage($meta: MetadataInput!, $layoutId: ID!, $components: [ComponentInput!]!) {
create_page(
meta: $meta
layout_id: $layoutId
components: $components
component_ids: []
) {
id
meta {
title
description
}
}
}
`;
return this.request(mutation, {
meta: { title, description, keywords },
layoutId,
components,
});
}
async updatePage(id, title, description, keywords = []) {
const mutation = `
mutation UpdatePage($id: ID!, $meta: MetadataInput!) {
update_page(
id: $id
meta: $meta
components: []
layout_id: ""
component_ids: []
) {
id
meta {
title
description
}
}
}
`;
return this.request(mutation, { id, meta: { title, description, keywords } });
}
async deletePage(id) {
const mutation = `
mutation DeletePage($id: ID!) {
delete_page(id: $id) {
id
meta {
title
}
}
}
`;
return this.request(mutation, { id });
}
}
export default StormyCMS;

Building a Blog

Create Blog Post Structure

src/blog.js
import StormyCMS from './stormycms';
const cms = new StormyCMS(
process.env.STORMYCMS_API_KEY,
process.env.STORMYCMS_CLIENT_ID
);
// Create a new blog post
export async function createBlogPost(title, content, excerpt, tags = []) {
const components = [
{
name: 'BlogHeader',
text: [{ locale: 'en', value: title }],
props: [
{
key: 'className',
translations: [{ locale: 'en', value: 'blog-post-header' }]
}
]
},
{
name: 'BlogContent',
text: [{ locale: 'en', value: content }],
props: [
{
key: 'className',
translations: [{ locale: 'en', value: 'blog-post-content' }]
}
]
},
{
name: 'BlogExcerpt',
text: [{ locale: 'en', value: excerpt }],
props: [
{
key: 'className',
translations: [{ locale: 'en', value: 'blog-post-excerpt' }]
}
]
}
];
return await cms.createPage(
title,
excerpt,
tags,
'blog-layout', // Your blog layout ID
components
);
}
// Get all blog posts
export async function getBlogPosts(limit = 10, offset = 0) {
const pages = await cms.getPages(limit, offset);
return pages.pages.filter(page =>
page.layout_id === 'blog-layout'
);
}
// Get a single blog post
export async function getBlogPost(id) {
return await cms.getPage(id);
}

Display Blog Posts

components/BlogList.jsx
import React, { useState, useEffect } from 'react';
import { getBlogPosts } from '../src/blog';
function BlogList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function loadPosts() {
try {
const data = await getBlogPosts();
setPosts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadPosts();
}, []);
if (loading) return <div>Loading posts...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="blog-list">
{posts.map(post => (
<article key={post.id} className="blog-post-preview">
<h2>{post.meta.title}</h2>
<p>{post.meta.description}</p>
<a href={`/blog/${post.id}`}>Read more</a>
</article>
))}
</div>
);
}
export default BlogList;

Creating a Landing Page

src/landing.js
import StormyCMS from './stormycms';
const cms = new StormyCMS(
process.env.STORMYCMS_API_KEY,
process.env.STORMYCMS_CLIENT_ID
);
export async function createLandingPage(data) {
const components = [
{
name: 'HeroSection',
text: [{ locale: 'en', value: data.headline }],
props: [
{
key: 'subheadline',
translations: [{ locale: 'en', value: data.subheadline }]
},
{
key: 'ctaText',
translations: [{ locale: 'en', value: data.ctaText }]
},
{
key: 'backgroundImage',
translations: [{ locale: 'en', value: data.backgroundImage }]
}
]
},
{
name: 'FeatureSection',
text: [{ locale: 'en', value: data.featuresTitle }],
props: [
{
key: 'features',
translations: [{
locale: 'en',
value: JSON.stringify(data.features)
}]
}
]
},
{
name: 'TestimonialSection',
text: [{ locale: 'en', value: data.testimonialsTitle }],
props: [
{
key: 'testimonials',
translations: [{
locale: 'en',
value: JSON.stringify(data.testimonials)
}]
}
]
},
{
name: 'FooterSection',
text: [{ locale: 'en', value: data.footerText }],
props: [
{
key: 'links',
translations: [{
locale: 'en',
value: JSON.stringify(data.footerLinks)
}]
}
]
}
];
return await cms.createPage(
data.title,
data.description,
data.keywords,
'landing-layout',
components
);
}
// Usage
const landingData = {
title: 'Welcome to Our Product',
description: 'The best solution for your needs',
keywords: ['product', 'solution', 'innovation'],
headline: 'Transform Your Business Today',
subheadline: 'Join thousands of satisfied customers',
ctaText: 'Get Started Free',
backgroundImage: '/images/hero-bg.jpg',
featuresTitle: 'Why Choose Us',
features: [
{ title: 'Fast', description: 'Lightning quick performance' },
{ title: 'Secure', description: 'Enterprise-grade security' },
{ title: 'Scalable', description: 'Grows with your business' }
],
testimonialsTitle: 'What Our Customers Say',
testimonials: [
{ name: 'John Doe', text: 'Amazing product!', company: 'Tech Corp' },
{ name: 'Jane Smith', text: 'Changed everything', company: 'Design Inc' }
],
footerText: '© 2024 Your Company',
footerLinks: [
{ text: 'About', url: '/about' },
{ text: 'Contact', url: '/contact' }
]
};
createLandingPage(landingData);

Managing Products

src/products.js
import StormyCMS from './stormycms';
const cms = new StormyCMS(
process.env.STORMYCMS_API_KEY,
process.env.STORMYCMS_CLIENT_ID
);
export async function createProduct(product) {
const components = [
{
name: 'ProductHeader',
text: [{ locale: 'en', value: product.name }],
props: [
{
key: 'price',
translations: [{ locale: 'en', value: product.price.toString() }]
},
{
key: 'currency',
translations: [{ locale: 'en', value: product.currency }]
},
{
key: 'sku',
translations: [{ locale: 'en', value: product.sku }]
}
]
},
{
name: 'ProductDescription',
text: [{ locale: 'en', value: product.description }],
props: [
{
key: 'shortDescription',
translations: [{ locale: 'en', value: product.shortDescription }]
}
]
},
{
name: 'ProductImages',
props: [
{
key: 'images',
translations: [{
locale: 'en',
value: JSON.stringify(product.images)
}]
},
{
key: 'thumbnail',
translations: [{ locale: 'en', value: product.thumbnail }]
}
]
},
{
name: 'ProductSpecs',
props: [
{
key: 'specifications',
translations: [{
locale: 'en',
value: JSON.stringify(product.specifications)
}]
}
]
}
];
return await cms.createPage(
product.name,
product.shortDescription,
product.tags,
'product-layout',
components
);
}
// Example product
const newProduct = {
name: 'Premium Widget',
price: 99.99,
currency: 'USD',
sku: 'WIDGET-001',
description: 'This is a detailed description of our premium widget...',
shortDescription: 'High-quality widget for all your needs',
images: [
'/images/widget-1.jpg',
'/images/widget-2.jpg',
'/images/widget-3.jpg'
],
thumbnail: '/images/widget-thumb.jpg',
specifications: {
weight: '2.5 lbs',
dimensions: '10x8x6 inches',
material: 'Aluminum',
warranty: '2 years'
},
tags: ['widget', 'premium', 'aluminum']
};
createProduct(newProduct);

Multi-language Support

src/i18n.js
import StormyCMS from './stormycms';
const cms = new StormyCMS(
process.env.STORMYCMS_API_KEY,
process.env.STORMYCMS_CLIENT_ID
);
export async function createMultiLanguagePage(data) {
const components = [
{
name: 'MultiLangContent',
text: [
{ locale: 'en', value: data.content.en },
{ locale: 'es', value: data.content.es },
{ locale: 'fr', value: data.content.fr }
],
props: [
{
key: 'title',
translations: [
{ locale: 'en', value: data.title.en },
{ locale: 'es', value: data.title.es },
{ locale: 'fr', value: data.title.fr }
]
}
]
}
];
const meta = {
title: data.title.en, // Primary language
description: data.description.en,
keywords: data.keywords.en
};
return await cms.createPage(
meta.title,
meta.description,
meta.keywords,
'multilang-layout',
components
);
}
// Usage
const multiLangData = {
title: {
en: 'Welcome',
es: 'Bienvenido',
fr: 'Bienvenue'
},
content: {
en: 'Welcome to our website!',
es: '¡Bienvenido a nuestro sitio web!',
fr: 'Bienvenue sur notre site web!'
},
description: {
en: 'A multi-language website',
es: 'Un sitio web multiidioma',
fr: 'Un site web multilingue'
},
keywords: {
en: ['welcome', 'website'],
es: ['bienvenido', 'sitio web'],
fr: ['bienvenue', 'site web']
}
};
createMultiLanguagePage(multiLangData);

React Integration

hooks/useStormyCMS.js
import { useState, useEffect } from 'react';
import StormyCMS from '../stormycms';
export function useStormyPages(limit = 20, offset = 0) {
const [pages, setPages] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const cms = new StormyCMS(
process.env.REACT_APP_STORMYCMS_API_KEY,
process.env.REACT_APP_STORMYCMS_CLIENT_ID
);
async function loadPages() {
try {
const data = await cms.getPages(limit, offset);
setPages(data.pages);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadPages();
}, [limit, offset]);
return { pages, loading, error };
}
export function useStormyPage(id) {
const [page, setPage] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!id) return;
const cms = new StormyCMS(
process.env.REACT_APP_STORMYCMS_API_KEY,
process.env.REACT_APP_STORMYCMS_CLIENT_ID
);
async function loadPage() {
try {
const data = await cms.getPage(id);
setPage(data.page);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadPage();
}, [id]);
return { page, loading, error };
}

Next.js Integration

lib/stormycms.js
import { GraphQLClient } from 'graphql-request';
const client = new GraphQLClient('https://api.stormycms.com/graphql', {
headers: {
'x-site-api-key': process.env.STORMYCMS_API_KEY,
'x-client-id': process.env.STORMYCMS_CLIENT_ID,
},
});
export async function getPages(limit = 20, offset = 0) {
const query = `
query GetPages($limit: Int, $offset: Int) {
pages(limit: $limit, offset: $offset) {
id
meta {
title
description
}
slug
}
}
`;
const data = await client.request(query, { limit, offset });
return data.pages;
}
export async function getPage(id) {
const query = `
query GetPage($id: ID!) {
page(id: $id) {
id
meta {
title
description
}
components {
id
name
text
props
}
}
}
`;
const data = await client.request(query, { id });
return data.page;
}
// pages/blog/index.js
export async function getStaticProps() {
const posts = await getPages(20, 0);
return {
props: {
posts,
},
revalidate: 60, // Revalidate every minute
};
}
export default function Blog({ posts }) {
return (
<div>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.meta.title}</h2>
<p>{post.meta.description}</p>
<Link href={`/blog/${post.id}`}>Read more</Link>
</article>
))}
</div>
);
}

Node.js Backend

server.js
import express from 'express';
import StormyCMS from './stormycms';
const app = express();
app.use(express.json());
const cms = new StormyCMS(
process.env.STORMYCMS_API_KEY,
process.env.STORMYCMS_CLIENT_ID
);
// API Routes
app.get('/api/pages', async (req, res) => {
try {
const { limit = 20, offset = 0 } = req.query;
const pages = await cms.getPages(parseInt(limit), parseInt(offset));
res.json(pages);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/pages/:id', async (req, res) => {
try {
const page = await cms.getPage(req.params.id);
res.json(page);
} catch (error) {
res.status(404).json({ error: 'Page not found' });
}
});
app.post('/api/pages', async (req, res) => {
try {
const { title, description, keywords, layoutId, components } = req.body;
const page = await cms.createPage(title, description, keywords, layoutId, components);
res.status(201).json(page);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.put('/api/pages/:id', async (req, res) => {
try {
const { title, description, keywords } = req.body;
const page = await cms.updatePage(req.params.id, title, description, keywords);
res.json(page);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.delete('/api/pages/:id', async (req, res) => {
try {
await cms.deletePage(req.params.id);
res.status(204).send();
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});

Best Practices

  1. Environment Variables: Never hardcode API keys
  2. Error Handling: Always wrap API calls in try-catch
  3. Caching: Implement caching for frequently accessed data
  4. Pagination: Use pagination for large datasets
  5. Validation: Validate data before sending mutations
  6. Rate Limiting: Respect API rate limits
  7. Type Safety: Use TypeScript for better development experience

Next Steps

Last updated: 2/27/26, 3:48 AM

StormyCMSPowerful Headless CMS with GraphQL API
Community
githubdiscordStormyCMS