GraphQL APIs in PHP
GraphQL is a query language for APIs that lets clients ask for exactly the data they need from a single endpoint. In this lesson you'll design a typed schema , write queries and mutations , back fields with resolvers using webonyx/graphql-php , and see why it solves REST's over- and under-fetching.
Learn GraphQL APIs in PHP in our free PHP course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ The Schema: A Typed Contract
Everything in GraphQL starts with the schema , written in the Schema Definition Language (SDL) . It declares your types (like User and Post ), their fields, and the relationships between them. Two special types are the entry points: Query lists every read operation, and Mutation lists every write. A trailing ! marks a field as non-nullable. Because the schema is typed, the server can validate any incoming query before running a line of resolver code.
This one document is the whole API's contract. It also self-documents: tools can read it to give clients autocompletion and validation, which is a big part of GraphQL's appeal.
2️⃣ Queries: Reading Exactly What You Need
A query reads data. The client writes the shape it wants — which fields, and how deep — and the server returns JSON in exactly that shape. Ask for three fields and you get three fields; ask for nested posts and they come back inside the user. This is the heart of GraphQL: the client controls the response shape, so there are no wasted bytes and no second request for related data.
Notice how the response mirrors the request one-to-one. That predictability makes GraphQL responses easy to consume on the client.
3️⃣ Mutations: Changing Data
Reads use queries; anything that changes data uses a mutation — creating, updating, or deleting. The syntax is nearly identical, but the operation keyword is mutation , and it maps to a field on the special Mutation type. Usefully, a mutation returns data too: you pick which fields of the newly changed object to read back, so a create can immediately hand you the new id .
Keeping reads (queries) and writes (mutations) separate is a clear, enforced convention — the schema makes it impossible to accidentally mutate data from a query.
4️⃣ Resolvers: Where PHP Does the Work
A schema describes what is possible; resolvers implement how . Every field is backed by a resolver function that returns its value — usually by reading a database, calling a service, or following a relationship. In PHP the standard engine is webonyx/graphql-php ( composer require webonyx/graphql-php ): you define types and their resolve callbacks, build a Schema , and call GraphQL::executeQuery() with the incoming query.
For Laravel, Lighthouse builds on this engine with a schema-first, directive-driven style so you write far less of this wiring by hand — but knowing the raw resolver model first means you understand what it's doing.
5️⃣ Why GraphQL? Over- and Under-Fetching
The clearest reason to reach for GraphQL is the data-fetching problem REST runs into. With REST, each endpoint returns a fixed shape : a screen that needs a user plus their posts plus comments often means several requests ( under-fetching ), while an endpoint that returns every field forces clients to download data they'll never show ( over-fetching ). GraphQL collapses that into one request against one endpoint, returning precisely the fields asked for.
Now you try the two core skills — finishing a schema and writing a query. Fill in each ___ using the 👉 hint, then check it against the Output panel.
One more. Write the smallest possible query that asks for just one field.
Common Errors (and the fix)
- "Cannot query field X on type Y" — you asked for a field that isn't in the schema (often a typo or a field you forgot to add). Check the type definition; the schema is the single source of truth.
- "Cannot return null for non-nullable field" — a resolver returned null for a field marked ! . Either make the field nullable (drop the ! ) or ensure the resolver always supplies a value.
- Using a query to change data — writes belong on the Mutation type. Define the operation under type Mutation and call it with the mutation keyword, not query .
- The N+1 problem on nested fields — resolving posts for many users fires one DB query per user. Batch them with a data-loader (or eager-load) so related records load in one query.
- Hitting multiple URLs like REST — there's only one endpoint. Don't append the type to the path; POST your whole query to /graphql .
Pro Tips
- 💡 Design the schema first. Agree on types and fields before writing resolvers — the schema is the contract both client and server build against.
- 💡 Use Lighthouse on Laravel. Its directives let you map fields straight to Eloquent models, cutting most resolver boilerplate.
- 💡 Limit query depth. Because clients choose the shape, add depth/complexity limits so a deeply nested query can't overload your server.
📋 Quick Reference — GraphQL in PHP
Concept / Snippet
What It Means
type User
Define an object type in SDL
String!
A non-nullable (required) field
type Query
Root type listing all read operations
type Mutation
Root type listing all write operations
query
Read data, choosing exact fields
mutation
Create, update, or delete data
resolve => fn(...)
Function that returns a field's value
webonyx/graphql-php
Core PHP GraphQL server engine
Lighthouse
Schema-first GraphQL for Laravel
Frequently Asked Questions
Mini-Challenge: A GraphQL Todo API
No code is filled in this time — just a brief and an outline. Build it with webonyx/graphql-php : define a Todo type, a query that lists todos, and a mutation that adds one, then wire the single endpoint. This is the full read-and-write GraphQL loop you'll use in real services.
🎉 Lesson Complete!
- ✅ A typed schema in SDL is the contract; ! marks non-nullable fields
- ✅ Queries read data and return exactly the fields you ask for, even nested
- ✅ Mutations create, update, and delete — and return chosen fields of the result
- ✅ Resolvers are PHP functions that produce each field's value
- ✅ One endpoint replaces many REST routes, fixing over- and under-fetching
- ✅ Build it with webonyx/graphql-php , or Lighthouse on Laravel
- ✅ Next lesson: Real-Time PHP with WebSockets — push live updates to clients
Practice quiz
How many HTTP endpoints does a typical GraphQL API expose?
- One endpoint for everything (usually /graphql)
- One per resource, like REST
- Two: one read, one write
- One per field
Answer: One endpoint for everything (usually /graphql). GraphQL exposes a single endpoint. The client describes exactly what it wants in the request body, rather than hitting many resource URLs.
In GraphQL, what is a 'query' used for?
- Defining the schema
- Creating new data
- Reading (fetching) data
- Deleting data
Answer: Reading (fetching) data. A query reads data. Operations that change data (create, update, delete) are mutations.
Which operation type changes data on the server?
- fragment
- mutation
- subscription
- query
Answer: mutation. Mutations create, update, or delete data. Queries are read-only by convention.
What is a resolver?
- A caching layer
- The HTTP router
- A database table
- A function that returns the value for a field
Answer: A function that returns the value for a field. Each field is backed by a resolver function that produces its value, often by reading a database or calling another service.
What problem does GraphQL solve compared to REST?
- Over-fetching and under-fetching of data
- It makes PHP run faster
- It removes the need for a database
- It encrypts traffic automatically
Answer: Over-fetching and under-fetching of data. REST endpoints return fixed shapes, causing over-fetching (too much) or under-fetching (needing multiple round trips). GraphQL lets the client request exactly the fields it needs.
Which PHP library is the de-facto core engine for building GraphQL servers?
- symfony/console
- webonyx/graphql-php
- phpunit/phpunit
- guzzlehttp/guzzle
Answer: webonyx/graphql-php. webonyx/graphql-php is the reference PHP implementation of the GraphQL spec, and most higher-level tools build on it.
Which package is the popular GraphQL solution specifically for Laravel?
- Eloquent
- Blade
- Horizon
- Lighthouse
Answer: Lighthouse. Lighthouse lets you build a GraphQL server in Laravel using a schema-first, directive-driven approach on top of webonyx/graphql-php.
What is the SDL in GraphQL?
- A database driver
- A query caching protocol
- The Schema Definition Language used to describe types and fields
- A PHP extension
Answer: The Schema Definition Language used to describe types and fields. The Schema Definition Language (SDL) is the typed, human-readable syntax for declaring object types, fields, queries, and mutations.
In the type 'type User { id: ID! name: String! }', what does the '!' mean?
- The field is private
- The field is non-nullable (required)
- The field is deprecated
- The field is a list
Answer: The field is non-nullable (required). A trailing ! marks a field as non-nullable: the server guarantees it will always return a value of that type.
Why is GraphQL strongly typed via its schema?
- So the schema documents the API and validates every query before execution
- To replace the need for HTTPS
- So clients can skip authentication
- To slow down responses
Answer: So the schema documents the API and validates every query before execution. The typed schema is a contract: it self-documents the API, powers tooling and autocompletion, and lets the server validate a query's shape before any resolver runs.
Continue this course
- Previous: Getting Started with Laravel
- Next: Real-Time PHP with WebSockets