Strapi REST API / GraphQL API Setup
Strapi automatically generates REST API and GraphQL for all content types. REST is available immediately, GraphQL requires a separate plugin. Both use the same permission system.
REST API: request structure
Strapi v4/v5 response format:
{
"data": {
"id": 1,
"attributes": { "title": "Article", "slug": "article", "publishedAt": "..." }
},
"meta": {}
}
Filtering:
# Simple filter
GET /api/articles?filters[status][$eq]=published
# Multiple conditions (AND)
GET /api/articles?filters[price][$gte]=100&filters[price][$lte]=500
# OR filter
GET /api/articles?filters[$or][0][title][$containsi]=react&filters[$or][1][content][$containsi]=react
# Operators: $eq, $ne, $lt, $lte, $gt, $gte, $in, $notIn, $contains, $containsi, $startsWith, $endsWith, $null, $notNull
Populate (relations and components):
# Fill all relations (1 level)
GET /api/articles?populate=*
# Specific fields
GET /api/articles?populate[author][fields][0]=name&populate[author][fields][1]=email
# Deep populate
GET /api/articles?populate[category][populate][icon]=true
# Dynamic zones
GET /api/pages?populate[sections][populate]=*
Pagination, sorting, fields:
# Pagination by pages
GET /api/articles?pagination[page]=2&pagination[pageSize]=10
# Cursor pagination (for infinite scroll)
GET /api/articles?pagination[start]=20&pagination[limit]=10
# Sorting
GET /api/articles?sort[0]=publishedAt:desc&sort[1]=title:asc
# Only needed fields
GET /api/articles?fields[0]=title&fields[1]=slug&fields[2]=excerpt
GraphQL
npm install @strapi/plugin-graphql
// config/plugins.js
module.exports = {
graphql: {
config: {
endpoint: '/graphql',
playgroundAlways: false, // false in production
defaultLimit: 10,
maxLimit: 100,
apolloServer: {
introspection: process.env.NODE_ENV !== 'production',
},
},
},
}
Playground: http://localhost:1337/graphql
Queries:
# List of articles with category
query GetArticles($locale: I18NLocaleCode, $page: Int) {
articles(
locale: $locale
pagination: { page: $page, pageSize: 10 }
sort: "publishedAt:desc"
filters: { publishedAt: { notNull: true } }
) {
data {
id
attributes {
title
slug
excerpt
publishedAt
cover {
data {
attributes { url alternativeText formats }
}
}
category {
data {
attributes { name slug }
}
}
}
}
meta {
pagination { total pageCount }
}
}
}
# Single article
query GetArticle($slug: String!) {
articles(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
title content publishedAt
author {
data { attributes { name bio avatar { data { attributes { url } } } } }
}
}
}
}
}
# Mutations (with authentication)
mutation CreateComment($articleId: ID!, $text: String!) {
createComment(data: { article: $articleId, text: $text }) {
data {
id
attributes { text createdAt }
}
}
}
GraphQL customization
// src/index.ts — add custom resolver
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension')
extensionService.use(({ nexus }) => ({
types: [
nexus.extendType({
type: 'Query',
definition(t) {
t.field('featuredArticles', {
type: 'ArticleEntityResponseCollection',
resolve: async (_root, _args, context) => {
const articles = await strapi.entityService.findMany(
'api::article.article',
{
filters: { featured: true },
populate: ['cover', 'category'],
sort: { publishedAt: 'desc' },
limit: 6,
}
)
return { data: articles }
},
})
},
}),
],
}))
},
}
API performance
// Limit maxPopulateDepth
// config/middlewares.js
{
name: 'strapi::populate-depth',
config: { maxDepth: 3 }
}
// Cache heavy queries with Redis
const redis = new Redis(process.env.REDIS_URL)
const cachedFeatured = await redis.get('featured-articles')
if (cachedFeatured) return JSON.parse(cachedFeatured)
const articles = await strapi.entityService.findMany(...)
await redis.setex('featured-articles', 300, JSON.stringify(articles))
Timeline
Setting up GraphQL plugin, custom resolvers, and query optimization — 2–3 days.







