Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Antony-Figueroa/my-evershop-app/llms.txt

Use this file to discover all available pages before exploring further.

GraphQL API

EverShop uses GraphQL for data fetching in React components. Extensions can add new types, extend existing types, and provide resolvers to fetch data from any source.

What is GraphQL in EverShop?

GraphQL provides:
  • Type-safe data fetching for React components
  • Flexible queries that request exactly the data needed
  • Extensible schema that can be augmented by any extension
  • Automatic data loading for page components
  • Resolver functions that can fetch from databases, APIs, or any data source
Every page component can export a query that automatically fetches data and passes it as props to the component.

Schema Files

GraphQL schemas are defined in .graphql files within extensions:
extensions/[name]/src/graphql/types/[TypeName]/
├── TypeName.graphql        # Type definitions
└── TypeName.resolvers.js   # Resolver functions

Creating GraphQL Types

1
Define the GraphQL schema
2
Create a .graphql file with your type definitions:
3
type Foo {
  id: ID!
  name: String!
  description: String
}

type Query {
  foo(id: ID!): Foo
  foos: [Foo!]!
}
4
Create resolvers
5
Implement the resolver functions:
6
const fooList = [
  { id: 1, name: 'Foo', description: 'This is a Foo object' },
  { id: 2, name: 'Bar', description: 'This is a Bar object' },
  { id: 3, name: 'Baz', description: 'This is a Baz object' }
];

export default {
  Query: {
    foo: (root, { id }) => {
      return fooList.find((foo) => foo.id === id);
    },
    foos: () => {
      return fooList;
    }
  },
  Foo: {
    id: (foo) => foo.id,
    name: (foo) => foo.name,
    description: (foo) => foo.description
  }
};
7
Use in components
8
Query the data in your page components:
9
import React from 'react';

type FooListProps = {
  foos?: {
    id: number;
    name: string;
    description: string;
  }[];
};

export default function FooList({ foos }: FooListProps) {
  return (
    <div className="foo-list container mx-auto px-4 py-8">
      <h2 className="font-bold text-center mb-8">Foo List</h2>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {foos?.map((foo) => (
          <div key={foo.id} className="bg-white rounded-lg shadow-md p-6">
            <h3 className="font-semibold mb-3">{foo.name}</h3>
            <p className="text-gray-600">{foo.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

export const layout = {
  areaId: 'content',
  sortOrder: 30
};

export const query = `
  query Query {
    foos {
      id
      name
      description
    }
  }
`;

Extending Existing Types

You can add fields to types defined by other extensions or the core system:
extensions/productCatalog/src/graphql/types/ProductExtension/ProductExtension.graphql
type Supplement {
  ingredients: String
  benefits: [String]
  presentation: String
  dosage: String
  warnings: String
  storage: String
}

type ProductExtension {
  supplement: Supplement
}

extend type Product {
  extension: ProductExtension
}
Use extend type TypeName to add fields to an existing type. The base type must be defined elsewhere (core or another extension).
Resolvers for extended types:
extensions/productCatalog/src/graphql/types/ProductExtension/ProductExtension.resolvers.js
export default {
  Product: {
    extension: (product) => {
      return {
        supplement: {
          ingredients: product.ingredients || null,
          benefits: product.benefits ? JSON.parse(product.benefits) : null,
          presentation: product.presentation || null,
          dosage: product.dosage || null,
          warnings: product.warnings || null,
          storage: product.storage || null
        }
      };
    }
  }
};

Resolver Structure

Resolvers are organized by type and field:
export default {
  // Root Query resolvers
  Query: {
    foo: (root, args, context) => {
      // Fetch single foo by ID
    },
    foos: (root, args, context) => {
      // Fetch list of foos
    }
  },
  
  // Root Mutation resolvers
  Mutation: {
    createFoo: (root, args, context) => {
      // Create new foo
    }
  },
  
  // Type field resolvers
  Foo: {
    id: (parent) => parent.id,
    name: (parent) => parent.name,
    // Computed field
    fullDescription: (parent) => {
      return `${parent.name}: ${parent.description}`;
    }
  }
};

Resolver Arguments

Every resolver receives four arguments:
(parent, args, context, info) => {
  // parent:  The result from the parent resolver
  // args:    Arguments passed to the field
  // context: Shared context (request, database, etc.)
  // info:    Query metadata (rarely used)
}
Query: {
  foo: (root, { id }, context) => {
    // root: Usually null for Query
    // id: Argument from query foo(id: "123")
    // context: { request, db, ... }
    return context.db.findFooById(id);
  }
}

Query Export in Components

Page components can export a GraphQL query that runs automatically:
export const query = `
  query Query {
    product(id: getContextValue("productId")) {
      productId
      name
      price
      description
    }
  }
`;

Context Values

Use getContextValue() to access runtime values:
query Query {
  product(id: getContextValue("productId")) {
    name
  }
  
  user(id: getContextValue("userId")) {
    email
  }
}

URL Generation

Get URLs for routes:
query Query {
  loginUrl: url(routeId: "login")
  productUrl: url(routeId: "product", params: { id: "123" })
}

Real-World Example: Product Reviews

From the productReviews extension:
import React from 'react';

interface Review {
  id: string;
  author: string;
  rating: number;
  comment: string;
  createdAt: string;
}

type ProductReviewsProps = {
  product: {
    productId: string;
    name: string;
  };
  reviews?: Review[];
  action?: string;
};

export default function ProductReviews({ 
  product, 
  reviews = [], 
  action 
}: ProductReviewsProps) {
  const averageRating = reviews.length > 0
    ? reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length
    : 0;

  return (
    <div className="bg-[#F8FAF9] border border-[#E8F5E9] rounded-lg p-6 mt-6">
      <h3 className="text-xl font-bold text-[#2D5A3D] mb-6">
        Reseñas de Clientes
      </h3>
      
      {reviews.length > 0 && (
        <div className="mb-6">
          <span className="text-[#4A5568]">
            {averageRating.toFixed(1)} de 5 ({reviews.length} reseñas)
          </span>
        </div>
      )}
      
      <div className="space-y-4">
        {reviews.map((review) => (
          <div key={review.id} className="border-b border-[#E8F5E9] pb-4">
            <span className="font-medium text-[#2D5A3D]">{review.author}</span>
            <p className="text-[#4A5568] text-sm">{review.comment}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

export const layout = {
  areaId: 'productPageBottom',
  sortOrder: 10
};

export const query = `
  query Query {
    product(id: getContextValue("productId")) {
      productId
      name
      reviews {
        id
        author
        rating
        comment
        createdAt
      }
    }
    action: url(routeId: "productReviews")
  }
`;

Scalar Types

GraphQL supports several scalar types:
TypeDescriptionExample
IDUnique identifier"123", "abc-def"
StringText value"Hello World"
IntInteger number42, -10
FloatDecimal number3.14, -0.5
BooleanTrue or falsetrue, false

Non-null Types

Use ! to mark fields as required:
type Product {
  id: ID!              # Required ID
  name: String!        # Required String
  description: String  # Optional String
  price: Float!        # Required Float
}

Lists

Use [] for arrays:
type Product {
  tags: [String]        # Optional array of optional strings
  tags: [String]!       # Required array of optional strings
  tags: [String!]!      # Required array of required strings
}

Mutations

Mutations modify data:
type Mutation {
  createReview(input: ReviewInput!): Review!
  updateReview(id: ID!, input: ReviewInput!): Review!
  deleteReview(id: ID!): Boolean!
}

input ReviewInput {
  author: String!
  rating: Int!
  comment: String!
}
With resolvers:
export default {
  Mutation: {
    createReview: async (root, { input }, context) => {
      const { db } = context;
      
      const result = await db.query(
        'INSERT INTO reviews (author_name, rating, comment) VALUES ($1, $2, $3) RETURNING *',
        [input.author, input.rating, input.comment]
      );
      
      return {
        id: result.rows[0].id,
        author: result.rows[0].author_name,
        rating: result.rows[0].rating,
        comment: result.rows[0].comment,
        createdAt: result.rows[0].created_at.toISOString()
      };
    }
  }
};

Best Practices

Each resolver should do one thing well:
// Good: Simple, focused resolver
Query: {
  products: (root, args, context) => {
    return context.db.getAllProducts();
  }
}

// Avoid: Resolver doing too much
Query: {
  products: async (root, args, context) => {
    const products = await context.db.getAllProducts();
    const reviews = await context.db.getAllReviews();
    const categories = await context.db.getAllCategories();
    // ... complex data manipulation
    return complexResult;
  }
}
Define types for your GraphQL data:
interface Foo {
  id: string;
  name: string;
  description?: string;
}

const fooList: Foo[] = [
  { id: '1', name: 'Foo', description: 'A foo object' }
];
Use DataLoader or batch queries to prevent performance issues:
// Bad: N+1 query problem
Product: {
  reviews: (product, args, context) => {
    // This runs once PER product!
    return context.db.getReviewsForProduct(product.id);
  }
}

// Better: Use DataLoader
Product: {
  reviews: (product, args, context) => {
    return context.reviewLoader.load(product.id);
  }
}
Add descriptions to types and fields:
"""A foo represents..."""
type Foo {
  """Unique identifier for the foo"""
  id: ID!
  
  """Display name of the foo"""
  name: String!
  
  """Optional description providing more details"""
  description: String
}

Testing GraphQL Queries

Using GraphQL Playground

EverShop includes GraphQL Playground (usually at http://localhost:3000/graphql):
query GetFoos {
  foos {
    id
    name
    description
  }
}

query GetFoo {
  foo(id: "1") {
    id
    name
    description
  }
}

mutation CreateReview {
  createReview(input: {
    author: "John Doe"
    rating: 5
    comment: "Great product!"
  }) {
    id
    author
    rating
  }
}

Using curl

curl -X POST http://localhost:3000/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ foos { id name } }"
  }'

Next Steps

  • Learn about Extensions for organizing your GraphQL code
  • Understand Routing for REST API alternatives
  • Explore Themes for using GraphQL in page components

Build docs developers (and LLMs) love