GraphQL API development gives every client one endpoint and a query language, so a screen asks for the fields it needs and leaves the rest on the server. A product page on the web and a product card in a mobile app can share one schema, and a new screen can add a field without a new route or a version bump.
Contents
At CodeWiz we design schemas, write resolvers and tune performance for GraphQL APIs in front of PostgreSQL, MySQL, existing REST services, search and queues. Typical servers are GraphQL Yoga, Apollo Server or Mercurius on Node.js, and Lighthouse on Laravel, with generated client types.
It fits product teams with dozens of overlapping REST endpoints, companies shipping web and mobile from the same data, and platforms that want partners to query without a new endpoint per integration. When every new screen becomes a negotiation about JSON shape, a schema is the more durable contract.

GraphQL API Development Starts With Schema Design
Schema design is the part clients feel every day. Types, fields and mutations are the public language of your product. A schema named after the domain stays stable when you split a table, add a cache or move a service, while a schema copied from tables leaks storage details into every app.
Model the business, then the storage
- Types use the words your team already uses: Order, Invoice, StockLevel, not rows from a join table.
- Lists that can grow use cursor pagination, with filters and sort arguments spelled out on the field.
- Mutations name the action: placeOrder, cancelShipment, publishArticle. A generic update with a bag of optional fields hides which combinations are legal.
- Nullability is a promise. A price that must exist is non-null; a discount that may be absent is nullable, and the description says why.
What you receive
- A schema in SDL, reviewed with the people who build the screens that will call it.
- An additive change policy: new fields are safe, removed fields go through deprecation with a reason and a date, and breaking changes are rare and explicit.
- Example operations for the main screens, which double as documentation and as the seed of the test suite.
- Code generation for TypeScript, Swift or Kotlin clients, so a renamed field fails the build instead of failing in production.
The GraphQL schema documentation is the reference for the type language. We shape the schema around your domain from that baseline.
Resolver Development Without Hidden Query Costs
A resolver is the function that loads data for one field. Written field by field against an ORM, a list of fifty products that each load a category becomes fifty-one database queries. That N+1 pattern is the usual reason a GraphQL API feels slow while the schema still looks elegant on paper.
We keep resolvers thin and push business rules into a service layer the rest of the application can call. REST endpoints, background jobs and the GraphQL API then share one implementation of “place an order” instead of three copies that slowly disagree.
How resolvers stay cheap
- Per-request batching with DataLoader, or the equivalent in Lighthouse and Mercurius, so related records are loaded once per query.
- Selection-aware reads where the stack allows it: if the client did not ask for the heavy description, the query does not fetch it.
- Search, recommendations and signed file URLs sit on fields that can fail or cache on their own, so one timeout does not blank the screen.
- Errors mapped to a small set of codes. A validation failure, a missing record and a downed downstream service are different outcomes, and the client can tell them apart.
const categoryLoader = new DataLoader(async (ids) => {
const rows = await db.categories.findByIds(ids);
const byId = new Map(rows.map((row) => [row.id, row]));
return ids.map((id) => byId.get(id) ?? null);
});
const resolvers = {
Product: {
category: (product, _args, ctx) =>
ctx.loaders.category.load(product.categoryId),
},
};The batching contract is documented in the graphql/dataloader repository. The work is applying it to every relationship in the schema.

Type System Design Clients Can Trust
The type system is the contract compilers and humans both read. Custom scalars, enums, interfaces and result unions remove a class of bugs where a currency is sometimes “eur” and sometimes “EUR”, or an error exists only inside a message string.
- Custom scalars for DateTime, Money, Email, URL and Locale, each with a written format. Money travels as a minor-unit integer plus a currency enum, so floating-point prices never appear on the wire.
- Separate input and output types. What a client may send when creating an order is a smaller, stricter shape than what the API returns after the order exists.
- Enums for closed sets such as order status, and strings only where the value is genuinely open, such as a customer note.
- Interfaces and unions when a feed mixes products, articles and banners that share a headline and an image but little else.
- Result types on mutations. placeOrder returns a union of OrderPlaced and a small set of domain failures (out of stock, payment declined) so the UI can branch without parsing human text.
| Schema choice | Fits when | A weaker fit when |
|---|---|---|
| Non-null field | The value is part of the promise, such as an order total | The value is often absent and clients must handle that |
| Cursor connection | Lists that page, filter or grow over time | A fixed set of three shipping methods |
| Interface or union | Several types share a meaningful subset of fields | Two types that only share an id |
| Mutation result union | The UI must show a specific, expected failure | Unexpected bugs, which should surface as real errors |
Each scalar and enum is documented in the schema description. GraphiQL shows that text to anyone exploring the API, and the same text feeds generated clients.
Performance Optimization for GraphQL API Development
We measure the operations your screens actually send, then remove the costs those operations pay. A faster process does not fix a query that loads half the catalogue on every keystroke.
- Persisted queries. Public apps send a hash instead of an arbitrary document. Unknown hashes are rejected, which makes response caching and abuse control practical.
- Depth, breadth and complexity limits. A single GraphQL request can fan out into thousands of field executions. Limits are part of the server config, with a higher budget for trusted first-party apps than for anonymous traffic.
- Response caching for public reads, in Redis or at the CDN, keyed by the operation and the viewer’s scope. Authenticated and anonymous responses never share a cache entry.
- Database indexes that match the filters and sorts exposed on the schema. A new filter argument is a product decision and an index decision at the same time.
- Field-level tracing in staging, and sampled in production, so a slow field shows up before users report a heavy screen.
When the underlying store is the limit, schema tweaks will not save you. Splitting or partitioning data is a separate design problem, covered in our guide to database sharding and partitioning. Search belongs in a search engine: a search field can resolve through Typesense instead of a SQL LIKE across large tables.

GraphQL, REST or a Deliberate Mix
GraphQL is a strong product API. It is a poor replacement for every HTTP endpoint you already have. Webhooks, file uploads, health checks and third parties that will only speak REST should stay REST. Many of the systems we build expose GraphQL to first-party apps and a smaller REST surface to everyone else.
| GraphQL | REST | gRPC | |
|---|---|---|---|
| Who shapes the payload | The client, within the schema | The server, per endpoint | The .proto contract |
| HTTP caching | Needs persisted queries or a custom cache key | Natural with GET and cache headers | Not aimed at browsers |
| Many client screens | One schema serves web, mobile and partners | Endpoints multiply with each screen | Generated stubs, weak browser story |
| Public integrators | Good once queries are persisted and documented | Familiar to most developers | Rarely what partners expect |
| Uploads and webhooks | Possible, awkward | The straightforward choice | Internal service calls |
Subscriptions fit a screen that must update live, such as an order status. They need long-lived connections and a pub/sub backplane, so a product that only refreshes on demand is better served by a query and a short cache. When three stable resources would do the job, we propose a small REST API instead of a schema and a client toolchain.
Security and Access Control on a GraphQL API
Authentication proves who is calling. Authorization decides which fields and rows they may see. Both checks belong in the design before the first resolver ships, because an unbounded read can still overload the database.
- Authentication at the edge: session cookies for first-party web apps, OAuth or JWT for mobile and partners. The resolver receives a viewer, and it does not parse tokens itself.
- Authorization on types and fields. Seeing a User does not imply seeing their email, and seeing an Order does not imply seeing every order in the tenant.
- Introspection off on the public production endpoint, or restricted to authenticated developers. Persisted queries replace free-form documents from browsers.
- Depth, complexity and alias limits, plus a request timeout. Anonymous traffic gets a tighter budget than your own apps.
- Rate limits that account for cost, not only for HTTP request count. A cheap request and a query that joins half the catalogue are not equal.
- Internal errors stay internal. Clients receive a stable code; stack traces and SQL stay in logs.
HTTP rate limiting still applies in front of the GraphQL endpoint. Our guide to API rate limiting covers the patterns, which we combine with query-cost limits. The OWASP GraphQL Cheat Sheet is the checklist we walk through before a schema is exposed beyond your own apps.
Common GraphQL Mistakes We Fix
- A schema generated once from tables and then treated as finished, so every storage change becomes a breaking API change.
- Resolvers that issue one query per field, with no batching and no tracing to show it.
- One giant input type reused by every mutation, with almost every field optional.
- Authorization implemented as “return null”, so missing data and forbidden data look the same and bugs hide for months.
- Introspection and unlimited query depth left open on a public endpoint.
- Business rules copied into resolvers, so a REST path and a GraphQL path accept different orders.
- No deprecation habit: fields disappear between deploys, and mobile apps that have not updated start failing.
What Drives the Cost of a GraphQL Project
We estimate after a short look at the domain, the clients and the systems the resolvers must call.
- How many types the product really needs, and how many client apps (web, iOS, Android, partners) will generate code from the schema.
- Whether the data model is already clear or still changing with the product.
- How many downstream systems a single screen touches: database, search, payments, a legacy REST service.
- Authorization depth: a single-tenant internal tool is a smaller job than field-level rules across tenants.
- Whether you need subscriptions, persisted queries and edge caching, or queries only.
- A one-off schema and server, or ongoing governance as new teams add fields.
GraphQL API development is scoped around those drivers, and the estimate lands before implementation starts. If a thinner REST API solves the same product problem, that is the proposal you will get.
How We Work
- Discovery: we map the screens, the existing endpoints, the data stores and the authorization rules, and we list the operations that hurt today.
- Schema: we draft the types and mutations with you, agree what is non-null, and freeze a first version the clients can try.
- Build: we implement resolvers, batching, auth, limits and code generation against that schema.
- Test: we cover the main operations with automated tests, load-test the hot queries and check that generated clients still compile.
- Launch: we put the API beside the current one and move screen by screen, with tracing on so slow fields show up early.
- Support: we keep a deprecation log, review new fields and adjust limits as real traffic arrives.
Why CodeWiz
- Full-stack, not a schema in isolation: we build the Laravel, Node.js, WordPress and mobile clients that call the API, so the schema is designed from both sides.
- Open-source first: Yoga, Apollo, Mercurius, Lighthouse and graphql-codegen keep you off a per-request licence.
- Tailored scope: GraphQL where many clients share one evolving model, REST where a resource and a webhook are enough.
- Fair pricing: premium development without premium prices, from Banja Luka, working remotely with clients locally and internationally since 2023.
- Performance and security together: query cost, caching, Cloudflare and rate limits are part of the same delivery. More about the studio is on the about CodeWiz page.
Frequently Asked Questions
How much does GraphQL API development cost?
It follows the size of the domain, the number of client apps and how many systems the resolvers must reach. A schema over one existing database is a short project. A partner API with field-level authorization and several downstream services is a larger one. You get a written estimate after discovery, before we write resolvers.
How long does it take to ship a first useful schema?
A first version covering the main screens of one product can often be in staging within a couple of weeks, including batching, auth and generated client types. Replacing a large REST surface screen by screen takes longer, because the migration is part of the work.
Should we replace our REST API entirely?
Keep REST for webhooks, uploads, health checks and integrators who expect it. Put GraphQL in front of the product screens that currently juggle several endpoints. Both can call the same service layer.
Is GraphQL slower than REST?
A query that replaces four REST round-trips is often faster on a mobile network, because the phone pays for one connection and one JSON document. It becomes slow when resolvers are unbatched or one query may traverse the whole graph. Batching, limits and tracing are part of the build for that reason.
Can you add GraphQL in front of Laravel or an existing Node.js API?
Yes. Lighthouse on Laravel and a Node.js server such as Yoga or Mercurius can sit on the application you already have. Resolvers call existing services and repositories, so the domain code stays in place.
Do mobile apps need a separate schema?
One schema serves both. Mobile and web select different fields from the same types. Separate schemas diverge as soon as a field changes in only one of them. A persisted-query allow-list per app is enough when each client should execute only its own operations.
Ready to Design a GraphQL API?
Tell us which screens are awkward to load today and which systems hold the data. We will propose a schema, a resolver approach and a rollout that fits the clients you already ship. Contact us to talk through the project, or see the full scope of our GraphQL development services.