You run an e-commerce or a management system with multiple frontends: web, mobile app, integrations with external marketplaces. With classic REST APIs every new view requires a new endpoint, and you often get too much or too little data. Need a dashboard showing orders, customer info and payment history all in one screen? You make three REST calls and assemble the data client side. Over-fetching, under-fetching, and a maintenance headache.
GraphQL solves this with a single endpoint and a clear spec of what you can ask for and how. At Meteora Web, we have been using it for years in projects that need data flexibility — products, variants, orders, customers, suppliers. We chose it to give frontends the power to request exactly what they need. But to use it well, you must master its three pillars: Schema, Query, Mutation and Subscription. This guide walks you from theory to practice with code you can copy and test immediately.
What is a GraphQL Schema and why is it the core of the API contract?
In REST APIs the contract is implicit: each endpoint returns a JSON structure defined by the backend. The frontend must adapt. GraphQL flips the model: the server exposes a schema that declares exactly which data types exist, their relationships, and which operations (query, mutation, subscription) are available. The client can introspect the schema and build precise requests.
A concrete example: imagine you run a clothing store (we, with Hibrido Abbigliamento, know retail inside out). A GraphQL schema could include a Product type with fields like id, name, price, variants (size, color, stock). Then an Order type linked to Customer and Product. In REST you'd have separate endpoints /products, /products/:id, /orders, /orders/:id. In GraphQL a single request can retrieve all nested data.
Sponsored Protocol
# schema.graphql example for a store
type Product {
id: ID!
name: String!
price: Float!
description: String
variants: [Variant!]!
}
type Variant {
size: String
color: String
stock: Int!
}
type Customer {
id: ID!
name: String!
email: String!
}
type Order {
id: ID!
customer: Customer!
products: [Product!]!
total: Float!
date: String!
}
type Query {
products(category: String, first: Int): [Product!]!
product(id: ID!): Product
orders(customerId: ID!): [Order!]!
}
type Mutation {
createProduct(input: ProductInput!): Product
updateProduct(id: ID!, input: ProductInput!): Product
deleteProduct(id: ID!): Boolean!
}
input ProductInput {
name: String!
price: Float!
description: String
variants: [VariantInput!]
}
input VariantInput {
size: String
color: String
stock: Int!
}Whenever you look at the schema, you know exactly what you can ask for and what the server will return. No surprises. We use it for auto-generated documentation: tools like GraphQL Playground or Apollo Studio produce interactive docs from the schema. The frontend no longer has to wait for the backend to add a new field to an endpoint – if the type exists, it can be requested immediately.
Why custom types (scalar, enum, input) make the schema robust
GraphQL provides built-in types (String, Int, Float, Boolean, ID). But the real power comes from custom types. An enum like Category (CLOTHING, ACCESSORIES, SHOES) ensures values are always valid. An input type allows passing structured objects in mutations, avoiding scattered parameters. In a project for a client in Sicily, we defined a reusable Address type used in both create and update mutations: clean and consistent.
Sponsored Protocol
How to write Queries to get only the data you need?
A GraphQL query is a request that describes the exact shape of the response. No more over-fetching (receiving 50 fields when you need only 3) or under-fetching (making multiple calls for related data).
# Example query: latest 5 products in category "clothing" with variants
query {
products(category: "clothing", first: 5) {
id
name
price
variants {
size
stock
}
}
}This query returns only id, name, price, and variants. If the frontend also needs the description, just add the field — no backend changes required. Control moves to the client: a game changer for teams developing web and mobile separately.
Aliases, fragments and variables
- Aliases – run two queries of the same type with different names:
saleProducts: products(category: "sale") { ... }. - Fragments – reuse a set of fields:
fragment ProductFields on Product { id name price }. - Variables – make queries parametric without hard-coding. The client passes a JSON with the variables.
If you come from REST, note: there are no more different endpoints for different filters. The whole logic is in the query arguments.
What is the difference between Mutation and Query in operation flow?
Mutations modify data (create, update, delete). The key difference from queries is that mutations are executed sequentially (if you send multiple mutations, the first finishes before the second). Also, it's best practice for a mutation to return the modified data, so the frontend can update its local cache.
Sponsored Protocol
# Mutation: create a new product
mutation CreateProduct($input: ProductInput!) {
createProduct(input: $input) {
id
name
price
variants {
size
stock
}
}
}Variables to pass:
{
"input": {
"name": "Striped T-shirt",
"price": 34.90,
"variants": [
{"size": "M", "color": "blue", "stock": 10}
]
}
}We often see design mistakes where the mutation returns only a boolean. Then the frontend has to make another query to get the updated object — two unnecessary round trips. Always return the modified type or at least the fields that might have changed.
Pattern for handling errors in mutations
In REST, you often use status codes 400/500 and a message. In GraphQL, mutations can return a union type (success + error) for structured results. Example:
type Mutation {
createProduct(input: ProductInput!): ProductResult
}
type ProductResult {
success: Boolean!
product: Product
error: Error
}
type Error {
code: String!
message: String!
}Now the frontend always knows whether the operation succeeded, and if there's an error it gets a parseable object. Don't rely only on the top-level GraphQL errors array for business logic.
How and when to use Subscriptions for real-time updates?
Subscriptions allow the server to push data to the client when an event occurs. Under the hood they use WebSocket (or other protocols). They are ideal for live notifications, chat, order status updates, or real-time dashboards.
Sponsored Protocol
# Subscription: receive notification when a new order is created
subscription {
orderCreated {
id
total
customer {
name
email
}
}
}The client subscribes and stays connected. When an order is created (via a createOrder mutation), the server sends the data to all subscribed clients. We implemented a subscription for a client with an inventory system: when a product drops below the minimum threshold, the manager gets an instant alert without reloading the page.
When not to use subscriptions
- If the update can be handled by periodic polling (e.g., every 30 seconds) and immediate reactivity isn't needed.
- If your infrastructure doesn't support WebSocket (some shared hosting or serverless environments have limitations).
- If events are very frequent and the client would receive too many updates that could be handled with a simple periodic query.
In general, use subscriptions only for sporadic and critical events. For continuous updates (e.g., real-time prices from a marketplace), consider a dedicated WebSocket stream or Server-Sent Events.
GraphQL vs REST for your next project — when does it make sense?
GraphQL is not the answer to everything. We use it when:
- The frontend has varying data needs (mobile, web, third parties).
- The data model is highly interconnected (e.g., e-commerce, CRM, ERP).
- You need auto-documentation and versioning without multiplying endpoints.
REST remains simpler for projects with linear CRUD actions, little overlap between frontends, and where efficient HTTP caching (Varnish, CDN) is leveraged. We recommend evaluating your team: if the frontend is already familiar with GraphQL and the backend can allocate time for the setup, it's an investment that pays off. Otherwise, start with REST and add GraphQL as an external layer via Apollo Federation or similar.
Sponsored Protocol
What to do now
- Explore your existing schema (if you already use GraphQL) or create a minimal schema for a domain you know (e.g., a blog: Post, Author, Comment). Write it in a
.graphqlfile and validate it with GraphQL Code Generator or Apollo VS Code. - Test queries with variables in GraphQL Playground or Apollo Studio. Simulate a real case: get the last 10 orders for a customer with products and totals.
- Integrate a mutation in your frontend (Apollo Client or Relay). Try to create an object and display the returned ID.
- If you need real-time, add a subscription for notification events (e.g., new message, order received). Ensure your server supports WebSocket.
- Read the official documentation at graphql.org/learn/schema to dive deeper into each type.
At Meteora Web, we're here to support your team. In our pillar article on REST and GraphQL you'll find broader comparisons and architectural choices. But if you have a concrete project, talk to us: we'll evaluate together whether GraphQL is the right path for your numbers.